titan-cli 0.1.3__py3-none-any.whl → 0.1.5__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/core/config.py +3 -1
- titan_cli/core/plugins/models.py +35 -7
- titan_cli/core/plugins/plugin_registry.py +11 -2
- titan_cli/core/workflows/__init__.py +2 -1
- titan_cli/core/workflows/project_step_source.py +48 -30
- titan_cli/core/workflows/workflow_filter_service.py +14 -8
- titan_cli/core/workflows/workflow_registry.py +12 -1
- titan_cli/core/workflows/workflow_sources.py +1 -1
- titan_cli/engine/steps/ai_assistant_step.py +42 -7
- titan_cli/engine/workflow_executor.py +6 -1
- titan_cli/ui/tui/screens/plugin_config_wizard.py +40 -9
- titan_cli/ui/tui/screens/workflow_execution.py +8 -28
- titan_cli/ui/tui/textual_components.py +59 -6
- titan_cli/ui/tui/textual_workflow_executor.py +9 -1
- titan_cli/ui/tui/widgets/__init__.py +2 -0
- titan_cli/ui/tui/widgets/step_container.py +70 -0
- {titan_cli-0.1.3.dist-info → titan_cli-0.1.5.dist-info}/METADATA +6 -3
- {titan_cli-0.1.3.dist-info → titan_cli-0.1.5.dist-info}/RECORD +42 -40
- {titan_cli-0.1.3.dist-info → titan_cli-0.1.5.dist-info}/WHEEL +1 -1
- titan_plugin_git/clients/git_client.py +82 -4
- titan_plugin_git/plugin.py +3 -0
- titan_plugin_git/steps/ai_commit_message_step.py +33 -28
- titan_plugin_git/steps/branch_steps.py +18 -37
- titan_plugin_git/steps/commit_step.py +18 -22
- titan_plugin_git/steps/diff_summary_step.py +182 -0
- titan_plugin_git/steps/push_step.py +27 -11
- titan_plugin_git/steps/status_step.py +15 -18
- titan_plugin_git/workflows/commit-ai.yaml +5 -0
- titan_plugin_github/agents/pr_agent.py +15 -2
- titan_plugin_github/steps/ai_pr_step.py +12 -21
- titan_plugin_github/steps/create_pr_step.py +17 -7
- titan_plugin_github/steps/github_prompt_steps.py +52 -0
- titan_plugin_github/steps/issue_steps.py +28 -14
- titan_plugin_github/steps/preview_step.py +11 -0
- titan_plugin_github/utils.py +5 -4
- titan_plugin_github/workflows/create-pr-ai.yaml +5 -0
- titan_plugin_jira/steps/ai_analyze_issue_step.py +8 -3
- titan_plugin_jira/steps/get_issue_step.py +16 -12
- titan_plugin_jira/steps/prompt_select_issue_step.py +22 -9
- titan_plugin_jira/steps/search_saved_query_step.py +21 -19
- {titan_cli-0.1.3.dist-info → titan_cli-0.1.5.dist-info}/entry_points.txt +0 -0
- {titan_cli-0.1.3.dist-info → titan_cli-0.1.5.dist-info/licenses}/LICENSE +0 -0
|
@@ -6,7 +6,6 @@ Uses PRAgent to analyze branch context and generate PR content.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error, Skip
|
|
9
|
-
from titan_cli.ui.tui.widgets import Panel
|
|
10
9
|
|
|
11
10
|
from ..agents import PRAgent
|
|
12
11
|
from ..messages import msg
|
|
@@ -43,23 +42,24 @@ def ai_suggest_pr_description_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
43
42
|
if not ctx.textual:
|
|
44
43
|
return Error("Textual UI context is not available for this step.")
|
|
45
44
|
|
|
45
|
+
# Begin step container
|
|
46
|
+
ctx.textual.begin_step("AI PR Description")
|
|
47
|
+
|
|
46
48
|
# Check if AI is configured
|
|
47
49
|
if not ctx.ai or not ctx.ai.is_available():
|
|
48
|
-
ctx.textual.
|
|
49
|
-
|
|
50
|
-
text=msg.GitHub.AI.AI_NOT_CONFIGURED,
|
|
51
|
-
panel_type="info"
|
|
52
|
-
)
|
|
53
|
-
)
|
|
50
|
+
ctx.textual.text(msg.GitHub.AI.AI_NOT_CONFIGURED, markup="dim")
|
|
51
|
+
ctx.textual.end_step("skip")
|
|
54
52
|
return Skip(msg.GitHub.AI.AI_NOT_CONFIGURED)
|
|
55
53
|
|
|
56
54
|
# Get Git client
|
|
57
55
|
if not ctx.git:
|
|
56
|
+
ctx.textual.end_step("error")
|
|
58
57
|
return Error(msg.GitHub.AI.GIT_CLIENT_NOT_AVAILABLE)
|
|
59
58
|
|
|
60
59
|
# Get branch info
|
|
61
60
|
head_branch = ctx.get("pr_head_branch")
|
|
62
61
|
if not head_branch:
|
|
62
|
+
ctx.textual.end_step("error")
|
|
63
63
|
return Error(msg.GitHub.AI.MISSING_PR_HEAD_BRANCH)
|
|
64
64
|
|
|
65
65
|
base_branch = ctx.git.main_branch
|
|
@@ -88,12 +88,8 @@ def ai_suggest_pr_description_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
88
88
|
|
|
89
89
|
# Check if PR content was generated (need commits in branch)
|
|
90
90
|
if not analysis.pr_title or not analysis.pr_body:
|
|
91
|
-
ctx.textual.
|
|
92
|
-
|
|
93
|
-
text="No commits found in branch to generate PR description.",
|
|
94
|
-
panel_type="info"
|
|
95
|
-
)
|
|
96
|
-
)
|
|
91
|
+
ctx.textual.text("No commits found in branch to generate PR description.", markup="dim")
|
|
92
|
+
ctx.textual.end_step("skip")
|
|
97
93
|
return Skip("No commits found for PR generation")
|
|
98
94
|
|
|
99
95
|
# Show PR size info
|
|
@@ -137,16 +133,9 @@ def ai_suggest_pr_description_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
137
133
|
|
|
138
134
|
if not use_ai_pr:
|
|
139
135
|
ctx.textual.text(msg.GitHub.AI.AI_SUGGESTION_REJECTED, markup="yellow")
|
|
136
|
+
ctx.textual.end_step("skip")
|
|
140
137
|
return Skip("User rejected AI-generated PR")
|
|
141
138
|
|
|
142
|
-
# Show success panel
|
|
143
|
-
ctx.textual.mount(
|
|
144
|
-
Panel(
|
|
145
|
-
text="AI generated PR description successfully",
|
|
146
|
-
panel_type="success"
|
|
147
|
-
)
|
|
148
|
-
)
|
|
149
|
-
|
|
150
139
|
# Success - save to context
|
|
151
140
|
metadata = {
|
|
152
141
|
"ai_generated": True,
|
|
@@ -155,6 +144,7 @@ def ai_suggest_pr_description_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
155
144
|
"pr_size": analysis.pr_size
|
|
156
145
|
}
|
|
157
146
|
|
|
147
|
+
ctx.textual.end_step("success")
|
|
158
148
|
return Success(
|
|
159
149
|
msg.GitHub.AI.AI_GENERATED_PR_DESCRIPTION_SUCCESS,
|
|
160
150
|
metadata=metadata
|
|
@@ -165,6 +155,7 @@ def ai_suggest_pr_description_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
165
155
|
ctx.textual.text(msg.GitHub.AI.AI_GENERATION_FAILED.format(e=e), markup="yellow")
|
|
166
156
|
ctx.textual.text(msg.GitHub.AI.FALLBACK_TO_MANUAL, markup="dim")
|
|
167
157
|
|
|
158
|
+
ctx.textual.end_step("skip")
|
|
168
159
|
return Skip(msg.GitHub.AI.AI_GENERATION_FAILED.format(e=e))
|
|
169
160
|
|
|
170
161
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# plugins/titan-plugin-github/titan_plugin_github/steps/create_pr_step.py
|
|
2
2
|
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
|
|
3
|
-
from titan_cli.ui.tui.widgets import Panel
|
|
4
3
|
from ..exceptions import GitHubAPIError
|
|
5
4
|
from ..messages import msg
|
|
6
5
|
|
|
@@ -33,10 +32,17 @@ def create_pr_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
33
32
|
if not ctx.textual:
|
|
34
33
|
return Error("Textual UI context is not available for this step.")
|
|
35
34
|
|
|
35
|
+
# Begin step container
|
|
36
|
+
ctx.textual.begin_step("Create Pull Request")
|
|
37
|
+
|
|
36
38
|
# 1. Get GitHub client from context
|
|
37
39
|
if not ctx.github:
|
|
40
|
+
ctx.textual.text("GitHub client is not available in the workflow context.", markup="red")
|
|
41
|
+
ctx.textual.end_step("error")
|
|
38
42
|
return Error("GitHub client is not available in the workflow context.")
|
|
39
43
|
if not ctx.git:
|
|
44
|
+
ctx.textual.text("Git client is not available in the workflow context.", markup="red")
|
|
45
|
+
ctx.textual.end_step("error")
|
|
40
46
|
return Error("Git client is not available in the workflow context.")
|
|
41
47
|
|
|
42
48
|
# 2. Get required data from context and client config
|
|
@@ -47,6 +53,8 @@ def create_pr_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
47
53
|
is_draft = ctx.get("pr_is_draft", False) # Default to not a draft
|
|
48
54
|
|
|
49
55
|
if not all([title, base, head]):
|
|
56
|
+
ctx.textual.text("Missing required context for creating a pull request: pr_title, pr_head_branch.", markup="red")
|
|
57
|
+
ctx.textual.end_step("error")
|
|
50
58
|
return Error(
|
|
51
59
|
"Missing required context for creating a pull request: pr_title, pr_head_branch."
|
|
52
60
|
)
|
|
@@ -63,24 +71,26 @@ def create_pr_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
63
71
|
|
|
64
72
|
# 4. Call the client method
|
|
65
73
|
try:
|
|
74
|
+
ctx.textual.text(f"Creating pull request '{title}' from {head} to {base}...", markup="dim")
|
|
66
75
|
pr = ctx.github.create_pull_request(
|
|
67
76
|
title=title, body=body, base=base, head=head, draft=is_draft, assignees=assignees
|
|
68
77
|
)
|
|
69
|
-
ctx.textual.
|
|
70
|
-
|
|
71
|
-
text=msg.GitHub.PR_CREATED.format(number=pr["number"], url=pr["url"]),
|
|
72
|
-
panel_type="success"
|
|
73
|
-
)
|
|
74
|
-
)
|
|
78
|
+
ctx.textual.text("") # spacing
|
|
79
|
+
ctx.textual.text(msg.GitHub.PR_CREATED.format(number=pr["number"], url=pr["url"]), markup="green")
|
|
75
80
|
|
|
76
81
|
# 4. Return Success with PR info
|
|
82
|
+
ctx.textual.end_step("success")
|
|
77
83
|
return Success(
|
|
78
84
|
"Pull request created successfully.",
|
|
79
85
|
metadata={"pr_number": pr["number"], "pr_url": pr["url"]},
|
|
80
86
|
)
|
|
81
87
|
except GitHubAPIError as e:
|
|
88
|
+
ctx.textual.text(f"Failed to create pull request: {e}", markup="red")
|
|
89
|
+
ctx.textual.end_step("error")
|
|
82
90
|
return Error(f"Failed to create pull request: {e}")
|
|
83
91
|
except Exception as e:
|
|
92
|
+
ctx.textual.text(f"An unexpected error occurred while creating the pull request: {e}", markup="red")
|
|
93
|
+
ctx.textual.end_step("error")
|
|
84
94
|
return Error(
|
|
85
95
|
f"An unexpected error occurred while creating the pull request: {e}"
|
|
86
96
|
)
|
|
@@ -23,18 +23,27 @@ def prompt_for_pr_title_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
23
23
|
if not ctx.textual:
|
|
24
24
|
return Error("Textual UI context is not available for this step.")
|
|
25
25
|
|
|
26
|
+
# Begin step container
|
|
27
|
+
ctx.textual.begin_step("Prompt for PR Title")
|
|
28
|
+
|
|
26
29
|
# Skip if title already exists (e.g., from AI generation)
|
|
27
30
|
if ctx.get("pr_title"):
|
|
31
|
+
ctx.textual.text("PR title already provided, skipping manual prompt.", markup="dim")
|
|
32
|
+
ctx.textual.end_step("skip")
|
|
28
33
|
return Skip("PR title already provided, skipping manual prompt.")
|
|
29
34
|
|
|
30
35
|
try:
|
|
31
36
|
title = ctx.textual.ask_text(msg.Prompts.ENTER_PR_TITLE)
|
|
32
37
|
if not title:
|
|
38
|
+
ctx.textual.end_step("error")
|
|
33
39
|
return Error("PR title cannot be empty.")
|
|
40
|
+
ctx.textual.end_step("success")
|
|
34
41
|
return Success("PR title captured", metadata={"pr_title": title})
|
|
35
42
|
except (KeyboardInterrupt, EOFError):
|
|
43
|
+
ctx.textual.end_step("error")
|
|
36
44
|
return Error("User cancelled.")
|
|
37
45
|
except Exception as e:
|
|
46
|
+
ctx.textual.end_step("error")
|
|
38
47
|
return Error(f"Failed to prompt for PR title: {e}", exception=e)
|
|
39
48
|
|
|
40
49
|
|
|
@@ -57,17 +66,25 @@ def prompt_for_pr_body_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
57
66
|
if not ctx.textual:
|
|
58
67
|
return Error("Textual UI context is not available for this step.")
|
|
59
68
|
|
|
69
|
+
# Begin step container
|
|
70
|
+
ctx.textual.begin_step("Prompt for PR Body")
|
|
71
|
+
|
|
60
72
|
# Skip if body already exists (e.g., from AI generation)
|
|
61
73
|
if ctx.get("pr_body"):
|
|
74
|
+
ctx.textual.text("PR body already provided, skipping manual prompt.", markup="dim")
|
|
75
|
+
ctx.textual.end_step("skip")
|
|
62
76
|
return Skip("PR body already provided, skipping manual prompt.")
|
|
63
77
|
|
|
64
78
|
try:
|
|
65
79
|
body = ctx.textual.ask_multiline(msg.Prompts.ENTER_PR_BODY, default="")
|
|
66
80
|
# Body can be empty
|
|
81
|
+
ctx.textual.end_step("success")
|
|
67
82
|
return Success("PR body captured", metadata={"pr_body": body})
|
|
68
83
|
except (KeyboardInterrupt, EOFError):
|
|
84
|
+
ctx.textual.end_step("error")
|
|
69
85
|
return Error("User cancelled.")
|
|
70
86
|
except Exception as e:
|
|
87
|
+
ctx.textual.end_step("error")
|
|
71
88
|
return Error(f"Failed to prompt for PR body: {e}", exception=e)
|
|
72
89
|
|
|
73
90
|
|
|
@@ -90,17 +107,25 @@ def prompt_for_issue_body_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
90
107
|
if not ctx.textual:
|
|
91
108
|
return Error("Textual UI context is not available for this step.")
|
|
92
109
|
|
|
110
|
+
# Begin step container
|
|
111
|
+
ctx.textual.begin_step("Prompt for Issue Body")
|
|
112
|
+
|
|
93
113
|
# Skip if body already exists (e.g., from AI generation)
|
|
94
114
|
if ctx.get("issue_body"):
|
|
115
|
+
ctx.textual.text("Issue body already provided, skipping manual prompt.", markup="dim")
|
|
116
|
+
ctx.textual.end_step("skip")
|
|
95
117
|
return Skip("Issue body already provided, skipping manual prompt.")
|
|
96
118
|
|
|
97
119
|
try:
|
|
98
120
|
body = ctx.textual.ask_multiline(msg.Prompts.ENTER_ISSUE_BODY, default="")
|
|
99
121
|
# Body can be empty
|
|
122
|
+
ctx.textual.end_step("success")
|
|
100
123
|
return Success("Issue body captured", metadata={"issue_body": body})
|
|
101
124
|
except (KeyboardInterrupt, EOFError):
|
|
125
|
+
ctx.textual.end_step("error")
|
|
102
126
|
return Error("User cancelled.")
|
|
103
127
|
except Exception as e:
|
|
128
|
+
ctx.textual.end_step("error")
|
|
104
129
|
return Error(f"Failed to prompt for issue body: {e}", exception=e)
|
|
105
130
|
|
|
106
131
|
|
|
@@ -111,7 +136,12 @@ def prompt_for_self_assign_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
111
136
|
if not ctx.textual:
|
|
112
137
|
return Error("Textual UI context is not available for this step.")
|
|
113
138
|
|
|
139
|
+
# Begin step container
|
|
140
|
+
ctx.textual.begin_step("Assign Issue")
|
|
141
|
+
|
|
114
142
|
if not ctx.github:
|
|
143
|
+
ctx.textual.text("GitHub client not available", markup="red")
|
|
144
|
+
ctx.textual.end_step("error")
|
|
115
145
|
return Error("GitHub client not available")
|
|
116
146
|
|
|
117
147
|
try:
|
|
@@ -121,11 +151,18 @@ def prompt_for_self_assign_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
121
151
|
if current_user not in assignees:
|
|
122
152
|
assignees.append(current_user)
|
|
123
153
|
ctx.set("assignees", assignees)
|
|
154
|
+
ctx.textual.text(f"Issue will be assigned to {current_user}", markup="green")
|
|
155
|
+
ctx.textual.end_step("success")
|
|
124
156
|
return Success(f"Issue will be assigned to {current_user}")
|
|
157
|
+
ctx.textual.text("Issue will not be assigned to current user", markup="dim")
|
|
158
|
+
ctx.textual.end_step("success")
|
|
125
159
|
return Success("Issue will not be assigned to current user")
|
|
126
160
|
except (KeyboardInterrupt, EOFError):
|
|
161
|
+
ctx.textual.end_step("error")
|
|
127
162
|
return Error("User cancelled.")
|
|
128
163
|
except Exception as e:
|
|
164
|
+
ctx.textual.text(f"Failed to prompt for self-assign: {e}", markup="red")
|
|
165
|
+
ctx.textual.end_step("error")
|
|
129
166
|
return Error(f"Failed to prompt for self-assign: {e}", exception=e)
|
|
130
167
|
|
|
131
168
|
|
|
@@ -136,12 +173,19 @@ def prompt_for_labels_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
136
173
|
if not ctx.textual:
|
|
137
174
|
return Error("Textual UI context is not available for this step.")
|
|
138
175
|
|
|
176
|
+
# Begin step container
|
|
177
|
+
ctx.textual.begin_step("Select Labels")
|
|
178
|
+
|
|
139
179
|
if not ctx.github:
|
|
180
|
+
ctx.textual.text("GitHub client not available", markup="red")
|
|
181
|
+
ctx.textual.end_step("error")
|
|
140
182
|
return Error("GitHub client not available")
|
|
141
183
|
|
|
142
184
|
try:
|
|
143
185
|
available_labels = ctx.github.list_labels()
|
|
144
186
|
if not available_labels:
|
|
187
|
+
ctx.textual.text("No labels found in the repository.", markup="dim")
|
|
188
|
+
ctx.textual.end_step("skip")
|
|
145
189
|
return Skip("No labels found in the repository.")
|
|
146
190
|
|
|
147
191
|
# Show available labels
|
|
@@ -164,8 +208,16 @@ def prompt_for_labels_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
164
208
|
selected_labels = []
|
|
165
209
|
|
|
166
210
|
ctx.set("labels", selected_labels)
|
|
211
|
+
if selected_labels:
|
|
212
|
+
ctx.textual.text(f"Selected labels: {', '.join(selected_labels)}", markup="green")
|
|
213
|
+
else:
|
|
214
|
+
ctx.textual.text("No labels selected", markup="dim")
|
|
215
|
+
ctx.textual.end_step("success")
|
|
167
216
|
return Success("Labels selected")
|
|
168
217
|
except (KeyboardInterrupt, EOFError):
|
|
218
|
+
ctx.textual.end_step("error")
|
|
169
219
|
return Error("User cancelled.")
|
|
170
220
|
except Exception as e:
|
|
221
|
+
ctx.textual.text(f"Failed to prompt for labels: {e}", markup="red")
|
|
222
|
+
ctx.textual.end_step("error")
|
|
171
223
|
return Error(f"Failed to prompt for labels: {e}", exception=e)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
from titan_cli.engine.context import WorkflowContext
|
|
3
3
|
from titan_cli.engine.results import WorkflowResult, Success, Error, Skip
|
|
4
|
-
from titan_cli.ui.tui.widgets import Panel
|
|
5
4
|
from ..agents.issue_generator import IssueGeneratorAgent
|
|
6
5
|
|
|
7
6
|
def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
@@ -12,14 +11,21 @@ def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult
|
|
|
12
11
|
if not ctx.textual:
|
|
13
12
|
return Error("Textual UI context is not available for this step.")
|
|
14
13
|
|
|
14
|
+
# Begin step container
|
|
15
|
+
ctx.textual.begin_step("Categorize and Generate Issue")
|
|
16
|
+
|
|
15
17
|
if not ctx.ai:
|
|
18
|
+
ctx.textual.text("AI client not available", markup="dim")
|
|
19
|
+
ctx.textual.end_step("skip")
|
|
16
20
|
return Skip("AI client not available")
|
|
17
21
|
|
|
18
22
|
issue_body_prompt = ctx.get("issue_body")
|
|
19
23
|
if not issue_body_prompt:
|
|
24
|
+
ctx.textual.text("issue_body not found in context", markup="red")
|
|
25
|
+
ctx.textual.end_step("error")
|
|
20
26
|
return Error("issue_body not found in context")
|
|
21
27
|
|
|
22
|
-
ctx.textual.text("Using AI to categorize and generate issue...", markup="
|
|
28
|
+
ctx.textual.text("Using AI to categorize and generate issue...", markup="dim")
|
|
23
29
|
|
|
24
30
|
try:
|
|
25
31
|
# Get available labels from repository for smart mapping
|
|
@@ -46,12 +52,8 @@ def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult
|
|
|
46
52
|
category = result["category"]
|
|
47
53
|
template_used = result.get("template_used", False)
|
|
48
54
|
|
|
49
|
-
ctx.textual.
|
|
50
|
-
|
|
51
|
-
text=f"Category detected: {category}",
|
|
52
|
-
panel_type="success"
|
|
53
|
-
)
|
|
54
|
-
)
|
|
55
|
+
ctx.textual.text("") # spacing
|
|
56
|
+
ctx.textual.text(f"Category detected: {category}", markup="green")
|
|
55
57
|
|
|
56
58
|
if template_used:
|
|
57
59
|
ctx.textual.text(f"Using template: {category}.md", markup="cyan")
|
|
@@ -63,8 +65,11 @@ def ai_suggest_issue_title_and_body_step(ctx: WorkflowContext) -> WorkflowResult
|
|
|
63
65
|
ctx.set("issue_category", category)
|
|
64
66
|
ctx.set("labels", result["labels"])
|
|
65
67
|
|
|
68
|
+
ctx.textual.end_step("success")
|
|
66
69
|
return Success(f"AI-generated issue ({category}) created successfully")
|
|
67
70
|
except Exception as e:
|
|
71
|
+
ctx.textual.text(f"Failed to generate issue: {e}", markup="red")
|
|
72
|
+
ctx.textual.end_step("error")
|
|
68
73
|
return Error(f"Failed to generate issue: {e}")
|
|
69
74
|
|
|
70
75
|
|
|
@@ -75,7 +80,12 @@ def create_issue_steps(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
75
80
|
if not ctx.textual:
|
|
76
81
|
return Error("Textual UI context is not available for this step.")
|
|
77
82
|
|
|
83
|
+
# Begin step container
|
|
84
|
+
ctx.textual.begin_step("Create Issue")
|
|
85
|
+
|
|
78
86
|
if not ctx.github:
|
|
87
|
+
ctx.textual.text("GitHub client not available", markup="red")
|
|
88
|
+
ctx.textual.end_step("error")
|
|
79
89
|
return Error("GitHub client not available")
|
|
80
90
|
|
|
81
91
|
issue_title = ctx.get("issue_title")
|
|
@@ -103,9 +113,13 @@ def create_issue_steps(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
103
113
|
labels = []
|
|
104
114
|
|
|
105
115
|
if not issue_title:
|
|
116
|
+
ctx.textual.text("issue_title not found in context", markup="red")
|
|
117
|
+
ctx.textual.end_step("error")
|
|
106
118
|
return Error("issue_title not found in context")
|
|
107
119
|
|
|
108
120
|
if not issue_body:
|
|
121
|
+
ctx.textual.text("issue_body not found in context", markup="red")
|
|
122
|
+
ctx.textual.end_step("error")
|
|
109
123
|
return Error("issue_body not found in context")
|
|
110
124
|
|
|
111
125
|
# Filter labels to only those that exist in the repository
|
|
@@ -123,21 +137,21 @@ def create_issue_steps(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
123
137
|
pass
|
|
124
138
|
|
|
125
139
|
try:
|
|
140
|
+
ctx.textual.text(f"Creating issue: {issue_title}...", markup="dim")
|
|
126
141
|
issue = ctx.github.create_issue(
|
|
127
142
|
title=issue_title,
|
|
128
143
|
body=issue_body,
|
|
129
144
|
assignees=assignees,
|
|
130
145
|
labels=labels,
|
|
131
146
|
)
|
|
132
|
-
ctx.textual.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
panel_type="success"
|
|
136
|
-
)
|
|
137
|
-
)
|
|
147
|
+
ctx.textual.text("") # spacing
|
|
148
|
+
ctx.textual.text(f"Successfully created issue #{issue.number}", markup="green")
|
|
149
|
+
ctx.textual.end_step("success")
|
|
138
150
|
return Success(
|
|
139
151
|
f"Successfully created issue #{issue.number}",
|
|
140
152
|
metadata={"issue": issue}
|
|
141
153
|
)
|
|
142
154
|
except Exception as e:
|
|
155
|
+
ctx.textual.text(f"Failed to create issue: {e}", markup="red")
|
|
156
|
+
ctx.textual.end_step("error")
|
|
143
157
|
return Error(f"Failed to create issue: {e}")
|
|
@@ -8,10 +8,15 @@ def preview_and_confirm_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
8
8
|
if not ctx.textual:
|
|
9
9
|
return Error("Textual UI context is not available for this step.")
|
|
10
10
|
|
|
11
|
+
# Begin step container
|
|
12
|
+
ctx.textual.begin_step("Preview and Confirm Issue")
|
|
13
|
+
|
|
11
14
|
issue_title = ctx.get("issue_title")
|
|
12
15
|
issue_body = ctx.get("issue_body")
|
|
13
16
|
|
|
14
17
|
if not issue_title or not issue_body:
|
|
18
|
+
ctx.textual.text("issue_title or issue_body not found in context", markup="red")
|
|
19
|
+
ctx.textual.end_step("error")
|
|
15
20
|
return Error("issue_title or issue_body not found in context")
|
|
16
21
|
|
|
17
22
|
# Show preview header
|
|
@@ -33,8 +38,14 @@ def preview_and_confirm_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
33
38
|
|
|
34
39
|
try:
|
|
35
40
|
if not ctx.textual.ask_confirm("Use this AI-generated issue?", default=True):
|
|
41
|
+
ctx.textual.text("User rejected AI-generated issue", markup="yellow")
|
|
42
|
+
ctx.textual.end_step("error")
|
|
36
43
|
return Error("User rejected AI-generated issue")
|
|
37
44
|
except (KeyboardInterrupt, EOFError):
|
|
45
|
+
ctx.textual.text("User cancelled operation", markup="red")
|
|
46
|
+
ctx.textual.end_step("error")
|
|
38
47
|
return Error("User cancelled operation")
|
|
39
48
|
|
|
49
|
+
ctx.textual.text("User confirmed AI-generated issue", markup="green")
|
|
50
|
+
ctx.textual.end_step("success")
|
|
40
51
|
return Success("User confirmed AI-generated issue")
|
titan_plugin_github/utils.py
CHANGED
|
@@ -54,21 +54,22 @@ def calculate_pr_size(diff: str) -> PRSizeEstimation:
|
|
|
54
54
|
files_changed = len(re.findall(file_pattern, diff, re.MULTILINE))
|
|
55
55
|
|
|
56
56
|
# Dynamic character limit based on PR size
|
|
57
|
+
# Increased limits to accommodate PR templates with images/GIFs
|
|
57
58
|
if files_changed <= 3 and diff_lines < 100:
|
|
58
59
|
# Small PR: bug fix, doc update, small feature
|
|
59
|
-
max_chars =
|
|
60
|
+
max_chars = 1500
|
|
60
61
|
pr_size = "small"
|
|
61
62
|
elif files_changed <= 10 and diff_lines < 500:
|
|
62
63
|
# Medium PR: feature, moderate refactor
|
|
63
|
-
max_chars =
|
|
64
|
+
max_chars = 2500
|
|
64
65
|
pr_size = "medium"
|
|
65
66
|
elif files_changed <= 30 and diff_lines < 2000:
|
|
66
67
|
# Large PR: architectural changes, new modules
|
|
67
|
-
max_chars =
|
|
68
|
+
max_chars = 4000
|
|
68
69
|
pr_size = "large"
|
|
69
70
|
else:
|
|
70
71
|
# Very large PR: major refactor, breaking changes
|
|
71
|
-
max_chars =
|
|
72
|
+
max_chars = 6000
|
|
72
73
|
pr_size = "very large"
|
|
73
74
|
|
|
74
75
|
return PRSizeEstimation(
|
|
@@ -19,6 +19,11 @@ steps:
|
|
|
19
19
|
plugin: git
|
|
20
20
|
step: get_current_branch
|
|
21
21
|
|
|
22
|
+
- id: branch_diff_summary
|
|
23
|
+
name: "Show Branch Changes Summary"
|
|
24
|
+
plugin: git
|
|
25
|
+
step: show_branch_diff_summary
|
|
26
|
+
|
|
22
27
|
# AI generates PR title and description from branch commits
|
|
23
28
|
- id: ai_pr_description
|
|
24
29
|
name: "AI PR Description"
|
|
@@ -3,7 +3,6 @@ AI-powered JIRA issue analysis step
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error, Skip
|
|
6
|
-
from titan_cli.ui.tui.widgets import Panel
|
|
7
6
|
from ..messages import msg
|
|
8
7
|
from ..agents import JiraAgent
|
|
9
8
|
from ..formatters import IssueAnalysisMarkdownFormatter
|
|
@@ -32,15 +31,20 @@ def ai_analyze_issue_requirements_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
32
31
|
if not ctx.textual:
|
|
33
32
|
return Error("Textual UI context is not available for this step.")
|
|
34
33
|
|
|
34
|
+
# Begin step container
|
|
35
|
+
ctx.textual.begin_step("AI Analyze Issue")
|
|
36
|
+
|
|
35
37
|
# Check if AI is available
|
|
36
38
|
if not ctx.ai or not ctx.ai.is_available():
|
|
37
|
-
ctx.textual.
|
|
39
|
+
ctx.textual.text(msg.Steps.AIIssue.AI_NOT_CONFIGURED_SKIP, markup="dim")
|
|
40
|
+
ctx.textual.end_step("skip")
|
|
38
41
|
return Skip(msg.Steps.AIIssue.AI_NOT_CONFIGURED)
|
|
39
42
|
|
|
40
43
|
# Get issue to analyze
|
|
41
44
|
issue = ctx.get("jira_issue") or ctx.get("selected_issue")
|
|
42
45
|
if not issue:
|
|
43
|
-
ctx.textual.
|
|
46
|
+
ctx.textual.text(msg.Steps.AIIssue.NO_ISSUE_FOUND, markup="red")
|
|
47
|
+
ctx.textual.end_step("error")
|
|
44
48
|
return Error(msg.Steps.AIIssue.NO_ISSUE_FOUND)
|
|
45
49
|
|
|
46
50
|
# Create JiraAgent instance and analyze issue with loading indicator
|
|
@@ -90,6 +94,7 @@ def ai_analyze_issue_requirements_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
90
94
|
"estimated_effort": analysis.estimated_effort
|
|
91
95
|
})
|
|
92
96
|
|
|
97
|
+
ctx.textual.end_step("success")
|
|
93
98
|
return Success(
|
|
94
99
|
"AI analysis completed",
|
|
95
100
|
metadata={
|
|
@@ -3,7 +3,6 @@ Get JIRA issue details step
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from titan_cli.engine import WorkflowContext, WorkflowResult, Success, Error
|
|
6
|
-
from titan_cli.ui.tui.widgets import Panel
|
|
7
6
|
from ..exceptions import JiraAPIError
|
|
8
7
|
from ..messages import msg
|
|
9
8
|
|
|
@@ -26,15 +25,20 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
26
25
|
if not ctx.textual:
|
|
27
26
|
return Error("Textual UI context is not available for this step.")
|
|
28
27
|
|
|
28
|
+
# Begin step container
|
|
29
|
+
ctx.textual.begin_step("Get Full Issue Details")
|
|
30
|
+
|
|
29
31
|
# Check if JIRA client is available
|
|
30
32
|
if not ctx.jira:
|
|
31
|
-
ctx.textual.
|
|
33
|
+
ctx.textual.text(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT, markup="red")
|
|
34
|
+
ctx.textual.end_step("error")
|
|
32
35
|
return Error(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT)
|
|
33
36
|
|
|
34
37
|
# Get issue key
|
|
35
38
|
issue_key = ctx.get("jira_issue_key")
|
|
36
39
|
if not issue_key:
|
|
37
|
-
ctx.textual.
|
|
40
|
+
ctx.textual.text("JIRA issue key is required", markup="red")
|
|
41
|
+
ctx.textual.end_step("error")
|
|
38
42
|
return Error("JIRA issue key is required")
|
|
39
43
|
|
|
40
44
|
# Get optional expand fields
|
|
@@ -46,12 +50,8 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
46
50
|
issue = ctx.jira.get_ticket(ticket_key=issue_key, expand=expand)
|
|
47
51
|
|
|
48
52
|
# Show success
|
|
49
|
-
ctx.textual.
|
|
50
|
-
|
|
51
|
-
text=msg.Steps.GetIssue.GET_SUCCESS.format(issue_key=issue_key),
|
|
52
|
-
panel_type="success"
|
|
53
|
-
)
|
|
54
|
-
)
|
|
53
|
+
ctx.textual.text("") # spacing
|
|
54
|
+
ctx.textual.text(msg.Steps.GetIssue.GET_SUCCESS.format(issue_key=issue_key), markup="green")
|
|
55
55
|
|
|
56
56
|
# Show issue details
|
|
57
57
|
ctx.textual.text(f" Title: {issue.summary}", markup="cyan")
|
|
@@ -60,6 +60,7 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
60
60
|
ctx.textual.text(f" Assignee: {issue.assignee or 'Unassigned'}")
|
|
61
61
|
ctx.textual.text("")
|
|
62
62
|
|
|
63
|
+
ctx.textual.end_step("success")
|
|
63
64
|
return Success(
|
|
64
65
|
msg.Steps.GetIssue.GET_SUCCESS.format(issue_key=issue_key),
|
|
65
66
|
metadata={"jira_issue": issue}
|
|
@@ -68,14 +69,17 @@ def get_issue_step(ctx: WorkflowContext) -> WorkflowResult:
|
|
|
68
69
|
except JiraAPIError as e:
|
|
69
70
|
if e.status_code == 404:
|
|
70
71
|
error_msg = msg.Steps.GetIssue.ISSUE_NOT_FOUND.format(issue_key=issue_key)
|
|
71
|
-
ctx.textual.
|
|
72
|
+
ctx.textual.text(error_msg, markup="red")
|
|
73
|
+
ctx.textual.end_step("error")
|
|
72
74
|
return Error(error_msg)
|
|
73
75
|
error_msg = msg.Steps.GetIssue.GET_FAILED.format(e=e)
|
|
74
|
-
ctx.textual.
|
|
76
|
+
ctx.textual.text(error_msg, markup="red")
|
|
77
|
+
ctx.textual.end_step("error")
|
|
75
78
|
return Error(error_msg)
|
|
76
79
|
except Exception as e:
|
|
77
80
|
error_msg = f"Unexpected error getting issue: {e}"
|
|
78
|
-
ctx.textual.
|
|
81
|
+
ctx.textual.text(error_msg, markup="red")
|
|
82
|
+
ctx.textual.end_step("error")
|
|
79
83
|
return Error(error_msg)
|
|
80
84
|
|
|
81
85
|
|