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.
Files changed (42) hide show
  1. titan_cli/core/config.py +3 -1
  2. titan_cli/core/plugins/models.py +35 -7
  3. titan_cli/core/plugins/plugin_registry.py +11 -2
  4. titan_cli/core/workflows/__init__.py +2 -1
  5. titan_cli/core/workflows/project_step_source.py +48 -30
  6. titan_cli/core/workflows/workflow_filter_service.py +14 -8
  7. titan_cli/core/workflows/workflow_registry.py +12 -1
  8. titan_cli/core/workflows/workflow_sources.py +1 -1
  9. titan_cli/engine/steps/ai_assistant_step.py +42 -7
  10. titan_cli/engine/workflow_executor.py +6 -1
  11. titan_cli/ui/tui/screens/plugin_config_wizard.py +40 -9
  12. titan_cli/ui/tui/screens/workflow_execution.py +8 -28
  13. titan_cli/ui/tui/textual_components.py +59 -6
  14. titan_cli/ui/tui/textual_workflow_executor.py +9 -1
  15. titan_cli/ui/tui/widgets/__init__.py +2 -0
  16. titan_cli/ui/tui/widgets/step_container.py +70 -0
  17. {titan_cli-0.1.3.dist-info → titan_cli-0.1.5.dist-info}/METADATA +6 -3
  18. {titan_cli-0.1.3.dist-info → titan_cli-0.1.5.dist-info}/RECORD +42 -40
  19. {titan_cli-0.1.3.dist-info → titan_cli-0.1.5.dist-info}/WHEEL +1 -1
  20. titan_plugin_git/clients/git_client.py +82 -4
  21. titan_plugin_git/plugin.py +3 -0
  22. titan_plugin_git/steps/ai_commit_message_step.py +33 -28
  23. titan_plugin_git/steps/branch_steps.py +18 -37
  24. titan_plugin_git/steps/commit_step.py +18 -22
  25. titan_plugin_git/steps/diff_summary_step.py +182 -0
  26. titan_plugin_git/steps/push_step.py +27 -11
  27. titan_plugin_git/steps/status_step.py +15 -18
  28. titan_plugin_git/workflows/commit-ai.yaml +5 -0
  29. titan_plugin_github/agents/pr_agent.py +15 -2
  30. titan_plugin_github/steps/ai_pr_step.py +12 -21
  31. titan_plugin_github/steps/create_pr_step.py +17 -7
  32. titan_plugin_github/steps/github_prompt_steps.py +52 -0
  33. titan_plugin_github/steps/issue_steps.py +28 -14
  34. titan_plugin_github/steps/preview_step.py +11 -0
  35. titan_plugin_github/utils.py +5 -4
  36. titan_plugin_github/workflows/create-pr-ai.yaml +5 -0
  37. titan_plugin_jira/steps/ai_analyze_issue_step.py +8 -3
  38. titan_plugin_jira/steps/get_issue_step.py +16 -12
  39. titan_plugin_jira/steps/prompt_select_issue_step.py +22 -9
  40. titan_plugin_jira/steps/search_saved_query_step.py +21 -19
  41. {titan_cli-0.1.3.dist-info → titan_cli-0.1.5.dist-info}/entry_points.txt +0 -0
  42. {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.mount(
49
- Panel(
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.mount(
92
- Panel(
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.mount(
70
- Panel(
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="cyan")
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.mount(
50
- Panel(
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.mount(
133
- Panel(
134
- text=f"Successfully created issue #{issue.number}",
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")
@@ -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 = 800
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 = 1800
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 = 3000
68
+ max_chars = 4000
68
69
  pr_size = "large"
69
70
  else:
70
71
  # Very large PR: major refactor, breaking changes
71
- max_chars = 4500
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.mount(Panel(msg.Steps.AIIssue.AI_NOT_CONFIGURED_SKIP, panel_type="info"))
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.mount(Panel(msg.Steps.AIIssue.NO_ISSUE_FOUND, panel_type="error"))
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.mount(Panel(msg.Plugin.CLIENT_NOT_AVAILABLE_IN_CONTEXT, panel_type="error"))
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.mount(Panel("JIRA issue key is required", panel_type="error"))
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.mount(
50
- Panel(
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.mount(Panel(error_msg, panel_type="error"))
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.mount(Panel(error_msg, panel_type="error"))
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.mount(Panel(error_msg, panel_type="error"))
81
+ ctx.textual.text(error_msg, markup="red")
82
+ ctx.textual.end_step("error")
79
83
  return Error(error_msg)
80
84
 
81
85