agentic-devtools 0.2.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.
- agdt_ai_helpers/__init__.py +34 -0
- agentic_devtools/__init__.py +8 -0
- agentic_devtools/background_tasks.py +598 -0
- agentic_devtools/cli/__init__.py +1 -0
- agentic_devtools/cli/azure_devops/__init__.py +222 -0
- agentic_devtools/cli/azure_devops/async_commands.py +1218 -0
- agentic_devtools/cli/azure_devops/auth.py +34 -0
- agentic_devtools/cli/azure_devops/commands.py +728 -0
- agentic_devtools/cli/azure_devops/config.py +49 -0
- agentic_devtools/cli/azure_devops/file_review_commands.py +1038 -0
- agentic_devtools/cli/azure_devops/helpers.py +561 -0
- agentic_devtools/cli/azure_devops/mark_reviewed.py +756 -0
- agentic_devtools/cli/azure_devops/pipeline_commands.py +724 -0
- agentic_devtools/cli/azure_devops/pr_summary_commands.py +579 -0
- agentic_devtools/cli/azure_devops/pull_request_details_commands.py +596 -0
- agentic_devtools/cli/azure_devops/review_commands.py +700 -0
- agentic_devtools/cli/azure_devops/review_helpers.py +191 -0
- agentic_devtools/cli/azure_devops/review_jira.py +308 -0
- agentic_devtools/cli/azure_devops/review_prompts.py +263 -0
- agentic_devtools/cli/azure_devops/run_details_commands.py +935 -0
- agentic_devtools/cli/azure_devops/vpn_toggle.py +1220 -0
- agentic_devtools/cli/git/__init__.py +91 -0
- agentic_devtools/cli/git/async_commands.py +294 -0
- agentic_devtools/cli/git/commands.py +399 -0
- agentic_devtools/cli/git/core.py +152 -0
- agentic_devtools/cli/git/diff.py +210 -0
- agentic_devtools/cli/git/operations.py +737 -0
- agentic_devtools/cli/jira/__init__.py +114 -0
- agentic_devtools/cli/jira/adf.py +105 -0
- agentic_devtools/cli/jira/async_commands.py +439 -0
- agentic_devtools/cli/jira/async_status.py +27 -0
- agentic_devtools/cli/jira/commands.py +28 -0
- agentic_devtools/cli/jira/comment_commands.py +141 -0
- agentic_devtools/cli/jira/config.py +69 -0
- agentic_devtools/cli/jira/create_commands.py +293 -0
- agentic_devtools/cli/jira/formatting.py +131 -0
- agentic_devtools/cli/jira/get_commands.py +287 -0
- agentic_devtools/cli/jira/helpers.py +278 -0
- agentic_devtools/cli/jira/parse_error_report.py +352 -0
- agentic_devtools/cli/jira/role_commands.py +560 -0
- agentic_devtools/cli/jira/state_helpers.py +39 -0
- agentic_devtools/cli/jira/update_commands.py +222 -0
- agentic_devtools/cli/jira/vpn_wrapper.py +58 -0
- agentic_devtools/cli/release/__init__.py +5 -0
- agentic_devtools/cli/release/commands.py +113 -0
- agentic_devtools/cli/release/helpers.py +113 -0
- agentic_devtools/cli/runner.py +318 -0
- agentic_devtools/cli/state.py +174 -0
- agentic_devtools/cli/subprocess_utils.py +109 -0
- agentic_devtools/cli/tasks/__init__.py +28 -0
- agentic_devtools/cli/tasks/commands.py +851 -0
- agentic_devtools/cli/testing.py +442 -0
- agentic_devtools/cli/workflows/__init__.py +80 -0
- agentic_devtools/cli/workflows/advancement.py +204 -0
- agentic_devtools/cli/workflows/base.py +240 -0
- agentic_devtools/cli/workflows/checklist.py +278 -0
- agentic_devtools/cli/workflows/commands.py +1610 -0
- agentic_devtools/cli/workflows/manager.py +802 -0
- agentic_devtools/cli/workflows/preflight.py +323 -0
- agentic_devtools/cli/workflows/worktree_setup.py +1110 -0
- agentic_devtools/dispatcher.py +704 -0
- agentic_devtools/file_locking.py +203 -0
- agentic_devtools/prompts/__init__.py +38 -0
- agentic_devtools/prompts/apply-pull-request-review-suggestions/default-initiate-prompt.md +82 -0
- agentic_devtools/prompts/create-jira-epic/default-initiate-prompt.md +63 -0
- agentic_devtools/prompts/create-jira-issue/default-initiate-prompt.md +306 -0
- agentic_devtools/prompts/create-jira-subtask/default-initiate-prompt.md +57 -0
- agentic_devtools/prompts/loader.py +377 -0
- agentic_devtools/prompts/pull-request-review/default-completion-prompt.md +45 -0
- agentic_devtools/prompts/pull-request-review/default-decision-prompt.md +63 -0
- agentic_devtools/prompts/pull-request-review/default-file-review-prompt.md +69 -0
- agentic_devtools/prompts/pull-request-review/default-initiate-prompt.md +50 -0
- agentic_devtools/prompts/pull-request-review/default-summary-prompt.md +40 -0
- agentic_devtools/prompts/update-jira-issue/default-initiate-prompt.md +78 -0
- agentic_devtools/prompts/work-on-jira-issue/default-checklist-creation-prompt.md +58 -0
- agentic_devtools/prompts/work-on-jira-issue/default-commit-prompt.md +47 -0
- agentic_devtools/prompts/work-on-jira-issue/default-completion-prompt.md +65 -0
- agentic_devtools/prompts/work-on-jira-issue/default-implementation-prompt.md +66 -0
- agentic_devtools/prompts/work-on-jira-issue/default-implementation-review-prompt.md +60 -0
- agentic_devtools/prompts/work-on-jira-issue/default-initiate-prompt.md +67 -0
- agentic_devtools/prompts/work-on-jira-issue/default-planning-prompt.md +50 -0
- agentic_devtools/prompts/work-on-jira-issue/default-pull-request-prompt.md +56 -0
- agentic_devtools/prompts/work-on-jira-issue/default-retrieve-prompt.md +29 -0
- agentic_devtools/prompts/work-on-jira-issue/default-setup-prompt.md +19 -0
- agentic_devtools/prompts/work-on-jira-issue/default-verification-prompt.md +73 -0
- agentic_devtools/state.py +754 -0
- agentic_devtools/task_state.py +902 -0
- agentic_devtools-0.2.0.dist-info/METADATA +544 -0
- agentic_devtools-0.2.0.dist-info/RECORD +92 -0
- agentic_devtools-0.2.0.dist-info/WHEEL +4 -0
- agentic_devtools-0.2.0.dist-info/entry_points.txt +79 -0
- agentic_devtools-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Worktree setup automation for workflows.
|
|
3
|
+
|
|
4
|
+
This module provides functions to automatically set up git worktrees,
|
|
5
|
+
install agentic-devtools, and open VS Code workspaces for workflow execution.
|
|
6
|
+
It also includes placeholder issue creation for create workflows.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import platform
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional, Tuple
|
|
16
|
+
|
|
17
|
+
# Exported for dynamic invocation by run_function_in_background
|
|
18
|
+
__all__ = ["_setup_worktree_from_state"]
|
|
19
|
+
|
|
20
|
+
# Name of the VS Code workspace file at repo root
|
|
21
|
+
WORKSPACE_FILE = "agdt-platform-management.code-workspace"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def generate_workflow_branch_name(
|
|
25
|
+
issue_key: str,
|
|
26
|
+
issue_type: str,
|
|
27
|
+
workflow_name: str,
|
|
28
|
+
parent_key: Optional[str] = None,
|
|
29
|
+
) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Generate a branch name based on issue type and workflow.
|
|
32
|
+
|
|
33
|
+
Patterns:
|
|
34
|
+
- Create workflows: <issueType>/<issue_key>/create-<issueType>
|
|
35
|
+
- Update workflows: <issueType>/<issue_key>/update-<issueType>
|
|
36
|
+
- Subtask create: subtask/<parent_key>/<issue_key>/create-subtask
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
issue_key: The Jira issue key (e.g., "DFLY-1234")
|
|
40
|
+
issue_type: The issue type (Task, Epic, Sub-task, Bug, etc.)
|
|
41
|
+
workflow_name: The workflow name (create-jira-issue, create-jira-epic, etc.)
|
|
42
|
+
parent_key: For subtasks, the parent issue key (e.g., "DFLY-1233")
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The branch name following the pattern
|
|
46
|
+
"""
|
|
47
|
+
# Normalize issue type to lowercase for branch naming
|
|
48
|
+
normalized_type = issue_type.lower().replace(" ", "-")
|
|
49
|
+
|
|
50
|
+
# Handle Sub-task specially
|
|
51
|
+
if normalized_type == "sub-task":
|
|
52
|
+
normalized_type = "subtask"
|
|
53
|
+
|
|
54
|
+
# Determine workflow action from workflow name
|
|
55
|
+
if "update" in workflow_name.lower():
|
|
56
|
+
action = f"update-{normalized_type}"
|
|
57
|
+
else:
|
|
58
|
+
action = f"create-{normalized_type}"
|
|
59
|
+
|
|
60
|
+
# For subtasks with a parent, include parent key
|
|
61
|
+
if normalized_type == "subtask" and parent_key:
|
|
62
|
+
return f"{normalized_type}/{parent_key}/{issue_key}/{action}"
|
|
63
|
+
|
|
64
|
+
# Standard pattern: <type>/<key>/<action>
|
|
65
|
+
return f"{normalized_type}/{issue_key}/{action}"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class WorktreeSetupResult:
|
|
70
|
+
"""Result of worktree setup operation."""
|
|
71
|
+
|
|
72
|
+
success: bool
|
|
73
|
+
worktree_path: str
|
|
74
|
+
branch_name: str
|
|
75
|
+
error_message: Optional[str] = None
|
|
76
|
+
vscode_opened: bool = False
|
|
77
|
+
helpers_installed: bool = False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_in_worktree() -> bool:
|
|
81
|
+
"""
|
|
82
|
+
Check if we're currently in a git worktree (not the main repo).
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
True if in a worktree, False if in main repo or not in a git repo.
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
# git rev-parse --is-inside-work-tree returns "true" if in a work tree
|
|
89
|
+
# git worktree list shows all worktrees
|
|
90
|
+
# Simplest check: compare git-dir to git-common-dir
|
|
91
|
+
result_dir = subprocess.run(
|
|
92
|
+
["git", "rev-parse", "--git-dir"],
|
|
93
|
+
capture_output=True,
|
|
94
|
+
text=True,
|
|
95
|
+
check=False,
|
|
96
|
+
)
|
|
97
|
+
result_common = subprocess.run(
|
|
98
|
+
["git", "rev-parse", "--git-common-dir"],
|
|
99
|
+
capture_output=True,
|
|
100
|
+
text=True,
|
|
101
|
+
check=False,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if result_dir.returncode != 0 or result_common.returncode != 0:
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
git_dir = Path(result_dir.stdout.strip()).resolve()
|
|
108
|
+
git_common_dir = Path(result_common.stdout.strip()).resolve()
|
|
109
|
+
|
|
110
|
+
# In main repo: git_dir == ".git" (resolves to same as git_common_dir)
|
|
111
|
+
# In worktree: git_dir is a file pointing elsewhere, or is different path
|
|
112
|
+
# The git-dir in a worktree points to .git/worktrees/<name>
|
|
113
|
+
return git_dir != git_common_dir
|
|
114
|
+
|
|
115
|
+
except (FileNotFoundError, OSError):
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_current_branch() -> Optional[str]:
|
|
120
|
+
"""
|
|
121
|
+
Get the current git branch name.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
The current branch name, or None if not in a git repo or detached HEAD.
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
result = subprocess.run(
|
|
128
|
+
["git", "branch", "--show-current"],
|
|
129
|
+
capture_output=True,
|
|
130
|
+
text=True,
|
|
131
|
+
check=False,
|
|
132
|
+
)
|
|
133
|
+
if result.returncode == 0:
|
|
134
|
+
return result.stdout.strip() or None
|
|
135
|
+
return None
|
|
136
|
+
except (FileNotFoundError, OSError):
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def switch_to_main_branch() -> bool:
|
|
141
|
+
"""
|
|
142
|
+
Switch to the main branch.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
True if switch was successful, False otherwise.
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
result = subprocess.run(
|
|
149
|
+
["git", "switch", "main"],
|
|
150
|
+
capture_output=True,
|
|
151
|
+
text=True,
|
|
152
|
+
check=False,
|
|
153
|
+
)
|
|
154
|
+
return result.returncode == 0
|
|
155
|
+
except (FileNotFoundError, OSError):
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_main_repo_root() -> Optional[str]:
|
|
160
|
+
"""
|
|
161
|
+
Get the root directory of the main git repository (not worktree).
|
|
162
|
+
|
|
163
|
+
For worktrees, this returns the path to the main repository.
|
|
164
|
+
For the main repo, this returns the repo root.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
The absolute path to the main repo root, or None if not in a git repo.
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
# First, get the common git directory (shared between main repo and worktrees)
|
|
171
|
+
result = subprocess.run(
|
|
172
|
+
["git", "rev-parse", "--git-common-dir"],
|
|
173
|
+
capture_output=True,
|
|
174
|
+
text=True,
|
|
175
|
+
check=False,
|
|
176
|
+
)
|
|
177
|
+
if result.returncode != 0:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
git_common_dir = result.stdout.strip()
|
|
181
|
+
|
|
182
|
+
# The git-common-dir is usually .git in main repo or path/to/main/.git for worktrees
|
|
183
|
+
# We need the parent of the .git directory
|
|
184
|
+
git_path = Path(git_common_dir).resolve()
|
|
185
|
+
|
|
186
|
+
# If it ends with .git, go to parent
|
|
187
|
+
if git_path.name == ".git":
|
|
188
|
+
return str(git_path.parent)
|
|
189
|
+
|
|
190
|
+
# For worktrees, git-common-dir points to main/.git directly
|
|
191
|
+
return str(git_path.parent)
|
|
192
|
+
|
|
193
|
+
except (FileNotFoundError, OSError):
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def get_repos_parent_dir() -> Optional[str]:
|
|
198
|
+
"""
|
|
199
|
+
Get the parent directory where repos are stored.
|
|
200
|
+
|
|
201
|
+
This is typically one level up from the main repo root (e.g., c:\\repos).
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
The absolute path to the repos parent directory, or None if not determinable.
|
|
205
|
+
"""
|
|
206
|
+
main_repo = get_main_repo_root()
|
|
207
|
+
if main_repo:
|
|
208
|
+
return str(Path(main_repo).parent)
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def create_worktree(
|
|
213
|
+
issue_key: str,
|
|
214
|
+
branch_prefix: str = "feature",
|
|
215
|
+
branch_name: Optional[str] = None,
|
|
216
|
+
use_existing_branch: bool = False,
|
|
217
|
+
) -> WorktreeSetupResult:
|
|
218
|
+
"""
|
|
219
|
+
Create a git worktree for the given issue key.
|
|
220
|
+
|
|
221
|
+
The worktree will be created as a sibling directory to the main repo,
|
|
222
|
+
named after the issue key (e.g., ../DFLY-1234).
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
issue_key: The issue key (e.g., "DFLY-1234")
|
|
226
|
+
branch_prefix: Prefix for the branch name (default: "feature").
|
|
227
|
+
Ignored if branch_name is provided.
|
|
228
|
+
branch_name: Exact branch name to use. If provided, branch_prefix is ignored.
|
|
229
|
+
Used for PR review workflows where the branch already exists on origin.
|
|
230
|
+
use_existing_branch: If True and branch_name is provided, checkout the
|
|
231
|
+
existing branch from origin instead of creating a new one.
|
|
232
|
+
Enables safety checks before proceeding.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
WorktreeSetupResult with success status and paths
|
|
236
|
+
"""
|
|
237
|
+
from ..git.operations import (
|
|
238
|
+
check_branch_safe_to_recreate,
|
|
239
|
+
fetch_branch,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
repos_parent = get_repos_parent_dir()
|
|
243
|
+
if not repos_parent:
|
|
244
|
+
return WorktreeSetupResult(
|
|
245
|
+
success=False,
|
|
246
|
+
worktree_path="",
|
|
247
|
+
branch_name="",
|
|
248
|
+
error_message="Could not determine repository parent directory",
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
worktree_path = os.path.join(repos_parent, issue_key)
|
|
252
|
+
|
|
253
|
+
# Determine the branch name to use
|
|
254
|
+
if branch_name:
|
|
255
|
+
resolved_branch_name = branch_name
|
|
256
|
+
else:
|
|
257
|
+
resolved_branch_name = f"{branch_prefix}/{issue_key}/implementation"
|
|
258
|
+
|
|
259
|
+
# Check if worktree already exists
|
|
260
|
+
if os.path.exists(worktree_path):
|
|
261
|
+
# Verify it's a valid git worktree
|
|
262
|
+
git_file = os.path.join(worktree_path, ".git")
|
|
263
|
+
if os.path.exists(git_file):
|
|
264
|
+
return WorktreeSetupResult(
|
|
265
|
+
success=True,
|
|
266
|
+
worktree_path=worktree_path,
|
|
267
|
+
branch_name=resolved_branch_name,
|
|
268
|
+
error_message=None,
|
|
269
|
+
)
|
|
270
|
+
else:
|
|
271
|
+
return WorktreeSetupResult(
|
|
272
|
+
success=False,
|
|
273
|
+
worktree_path=worktree_path,
|
|
274
|
+
branch_name=resolved_branch_name,
|
|
275
|
+
error_message=f"Directory {worktree_path} exists but is not a git worktree",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Check if we're currently on the target branch in the main repo.
|
|
279
|
+
# Git doesn't allow creating a worktree for a branch that's already checked out.
|
|
280
|
+
# If we're on the target branch in main repo, we need to switch to main first.
|
|
281
|
+
current_branch = get_current_branch()
|
|
282
|
+
in_worktree = is_in_worktree()
|
|
283
|
+
|
|
284
|
+
if current_branch == resolved_branch_name and not in_worktree:
|
|
285
|
+
print(f"Currently on branch '{resolved_branch_name}' in main repo.")
|
|
286
|
+
print("Switching to 'main' branch to allow worktree creation...")
|
|
287
|
+
if not switch_to_main_branch():
|
|
288
|
+
return WorktreeSetupResult(
|
|
289
|
+
success=False,
|
|
290
|
+
worktree_path=worktree_path,
|
|
291
|
+
branch_name=resolved_branch_name,
|
|
292
|
+
error_message="Failed to switch to main branch. Cannot create worktree while on target branch.",
|
|
293
|
+
)
|
|
294
|
+
print("Switched to 'main' branch successfully.")
|
|
295
|
+
|
|
296
|
+
# For PR review workflows with existing branches, perform safety checks
|
|
297
|
+
if use_existing_branch and branch_name:
|
|
298
|
+
print(f"Checking if branch '{branch_name}' is safe to use...")
|
|
299
|
+
|
|
300
|
+
# First fetch the branch from origin
|
|
301
|
+
fetch_branch(branch_name)
|
|
302
|
+
|
|
303
|
+
# Perform safety check
|
|
304
|
+
safety_result = check_branch_safe_to_recreate(branch_name)
|
|
305
|
+
|
|
306
|
+
if not safety_result.is_safe:
|
|
307
|
+
return WorktreeSetupResult(
|
|
308
|
+
success=False,
|
|
309
|
+
worktree_path=worktree_path,
|
|
310
|
+
branch_name=resolved_branch_name,
|
|
311
|
+
error_message=f"Cannot safely create worktree:\n{safety_result.message}",
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
print(f"Safety check passed: {safety_result.message}")
|
|
315
|
+
|
|
316
|
+
# Create the worktree
|
|
317
|
+
try:
|
|
318
|
+
print(f"Creating worktree at {worktree_path}...")
|
|
319
|
+
|
|
320
|
+
if use_existing_branch and branch_name:
|
|
321
|
+
# For PR review: checkout existing branch from origin
|
|
322
|
+
result = subprocess.run(
|
|
323
|
+
["git", "worktree", "add", worktree_path, branch_name],
|
|
324
|
+
capture_output=True,
|
|
325
|
+
text=True,
|
|
326
|
+
check=False,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
if result.returncode != 0:
|
|
330
|
+
# Try tracking the remote branch
|
|
331
|
+
result = subprocess.run(
|
|
332
|
+
["git", "worktree", "add", worktree_path, "--track", "-b", branch_name, f"origin/{branch_name}"],
|
|
333
|
+
capture_output=True,
|
|
334
|
+
text=True,
|
|
335
|
+
check=False,
|
|
336
|
+
)
|
|
337
|
+
else:
|
|
338
|
+
# Standard flow: create new branch
|
|
339
|
+
result = subprocess.run(
|
|
340
|
+
["git", "worktree", "add", worktree_path, "-b", resolved_branch_name],
|
|
341
|
+
capture_output=True,
|
|
342
|
+
text=True,
|
|
343
|
+
check=False,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
if result.returncode != 0:
|
|
347
|
+
# Check if branch already exists - try without -b
|
|
348
|
+
if "already exists" in result.stderr:
|
|
349
|
+
print(f"Branch {resolved_branch_name} already exists, using existing branch...")
|
|
350
|
+
result = subprocess.run(
|
|
351
|
+
["git", "worktree", "add", worktree_path, resolved_branch_name],
|
|
352
|
+
capture_output=True,
|
|
353
|
+
text=True,
|
|
354
|
+
check=False,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if result.returncode != 0:
|
|
358
|
+
return WorktreeSetupResult(
|
|
359
|
+
success=False,
|
|
360
|
+
worktree_path=worktree_path,
|
|
361
|
+
branch_name=resolved_branch_name,
|
|
362
|
+
error_message=f"Failed to create worktree: {result.stderr.strip()}",
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
print(f"Worktree created successfully at {worktree_path}")
|
|
366
|
+
return WorktreeSetupResult(
|
|
367
|
+
success=True,
|
|
368
|
+
worktree_path=worktree_path,
|
|
369
|
+
branch_name=resolved_branch_name,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
except (FileNotFoundError, OSError) as e:
|
|
373
|
+
return WorktreeSetupResult(
|
|
374
|
+
success=False,
|
|
375
|
+
worktree_path=worktree_path,
|
|
376
|
+
branch_name=branch_name,
|
|
377
|
+
error_message=f"Error creating worktree: {e}",
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def install_agentic_devtools(worktree_path: str) -> bool:
|
|
382
|
+
"""
|
|
383
|
+
Set up development tools in the worktree using setup-dev-tools.py.
|
|
384
|
+
|
|
385
|
+
This runs `python setup-dev-tools.py` in the worktree to:
|
|
386
|
+
- Create a local .dfly-venv with agentic-devtools installed
|
|
387
|
+
- Configure git hooks
|
|
388
|
+
- Install cspell and ruff
|
|
389
|
+
|
|
390
|
+
The venv approach enables multi-worktree development where each worktree
|
|
391
|
+
has its own isolated installation of agentic-devtools.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
worktree_path: Path to the worktree directory
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
True if setup succeeded, False otherwise
|
|
398
|
+
"""
|
|
399
|
+
setup_script = os.path.join(worktree_path, "setup-dev-tools.py")
|
|
400
|
+
|
|
401
|
+
if not os.path.exists(setup_script):
|
|
402
|
+
print(f"Warning: setup-dev-tools.py not found at {setup_script}", file=sys.stderr)
|
|
403
|
+
return False
|
|
404
|
+
|
|
405
|
+
print(f"Running setup-dev-tools.py in {worktree_path}...")
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
# Use the same Python interpreter that's running this script
|
|
409
|
+
python_exe = sys.executable
|
|
410
|
+
|
|
411
|
+
result = subprocess.run(
|
|
412
|
+
[python_exe, setup_script],
|
|
413
|
+
cwd=worktree_path,
|
|
414
|
+
capture_output=True,
|
|
415
|
+
text=True,
|
|
416
|
+
check=False,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
if result.returncode != 0:
|
|
420
|
+
print(f"Warning: Failed to run setup-dev-tools.py: {result.stderr}", file=sys.stderr)
|
|
421
|
+
# Print stdout too as it may contain useful info
|
|
422
|
+
if result.stdout:
|
|
423
|
+
print(result.stdout, file=sys.stderr)
|
|
424
|
+
return False
|
|
425
|
+
|
|
426
|
+
print("Development tools configured successfully in worktree")
|
|
427
|
+
return True
|
|
428
|
+
|
|
429
|
+
except (FileNotFoundError, OSError) as e:
|
|
430
|
+
print(f"Warning: Error running setup-dev-tools.py: {e}", file=sys.stderr)
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# Backward-compatible alias
|
|
435
|
+
install_agdt_ai_helpers = install_agentic_devtools
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def open_vscode_workspace(worktree_path: str) -> bool:
|
|
439
|
+
"""
|
|
440
|
+
Open VS Code with the workspace file in the worktree.
|
|
441
|
+
|
|
442
|
+
This opens a new VS Code window with the agdt-platform-management.code-workspace
|
|
443
|
+
file from the worktree.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
worktree_path: Path to the worktree directory
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
True if VS Code was opened, False otherwise
|
|
450
|
+
"""
|
|
451
|
+
workspace_file = os.path.join(worktree_path, WORKSPACE_FILE)
|
|
452
|
+
|
|
453
|
+
if not os.path.exists(workspace_file):
|
|
454
|
+
print(f"Warning: Workspace file not found at {workspace_file}", file=sys.stderr)
|
|
455
|
+
return False
|
|
456
|
+
|
|
457
|
+
print(f"Opening VS Code workspace: {workspace_file}")
|
|
458
|
+
|
|
459
|
+
try:
|
|
460
|
+
# Open VS Code in a new window (non-blocking)
|
|
461
|
+
# Check actual platform (not mocked) for subprocess flags availability
|
|
462
|
+
if platform.system() == "Windows" and hasattr(subprocess, "DETACHED_PROCESS"):
|
|
463
|
+
# On Windows, 'code' is a .cmd batch file, so we need shell=True
|
|
464
|
+
# to find it via PATH. We also use creationflags to detach the process.
|
|
465
|
+
subprocess.Popen(
|
|
466
|
+
["code", workspace_file],
|
|
467
|
+
stdout=subprocess.DEVNULL,
|
|
468
|
+
stderr=subprocess.DEVNULL,
|
|
469
|
+
shell=True,
|
|
470
|
+
creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP,
|
|
471
|
+
)
|
|
472
|
+
else:
|
|
473
|
+
# On Unix-like systems, start_new_session works correctly
|
|
474
|
+
subprocess.Popen(
|
|
475
|
+
["code", workspace_file],
|
|
476
|
+
stdout=subprocess.DEVNULL,
|
|
477
|
+
stderr=subprocess.DEVNULL,
|
|
478
|
+
start_new_session=True,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
print("VS Code window opened")
|
|
482
|
+
return True
|
|
483
|
+
|
|
484
|
+
except (FileNotFoundError, OSError) as e:
|
|
485
|
+
print(f"Warning: Could not open VS Code: {e}", file=sys.stderr)
|
|
486
|
+
return False
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def setup_worktree_environment(
|
|
490
|
+
issue_key: str,
|
|
491
|
+
branch_prefix: str = "feature",
|
|
492
|
+
branch_name: Optional[str] = None,
|
|
493
|
+
use_existing_branch: bool = False,
|
|
494
|
+
install_helpers: bool = True,
|
|
495
|
+
open_vscode: bool = True,
|
|
496
|
+
) -> WorktreeSetupResult:
|
|
497
|
+
"""
|
|
498
|
+
Complete worktree setup: create worktree, install helpers, open VS Code.
|
|
499
|
+
|
|
500
|
+
This is the main entry point for setting up a new development environment
|
|
501
|
+
for an issue. It:
|
|
502
|
+
1. Creates a git worktree for the issue
|
|
503
|
+
2. Installs agentic-devtools in the worktree
|
|
504
|
+
3. Opens VS Code with the workspace file
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
issue_key: The issue key (e.g., "DFLY-1234")
|
|
508
|
+
branch_prefix: Prefix for the branch name (default: "feature").
|
|
509
|
+
Ignored if branch_name is provided.
|
|
510
|
+
branch_name: Exact branch name to use. If provided, branch_prefix is ignored.
|
|
511
|
+
Used for PR review workflows where the branch already exists on origin.
|
|
512
|
+
use_existing_branch: If True and branch_name is provided, checkout the
|
|
513
|
+
existing branch from origin instead of creating a new one.
|
|
514
|
+
install_helpers: Whether to install agentic-devtools (default: True)
|
|
515
|
+
open_vscode: Whether to open VS Code (default: True)
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
WorktreeSetupResult with success status and details
|
|
519
|
+
"""
|
|
520
|
+
# Step 1: Create worktree
|
|
521
|
+
result = create_worktree(
|
|
522
|
+
issue_key=issue_key,
|
|
523
|
+
branch_prefix=branch_prefix,
|
|
524
|
+
branch_name=branch_name,
|
|
525
|
+
use_existing_branch=use_existing_branch,
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
if not result.success:
|
|
529
|
+
return result
|
|
530
|
+
|
|
531
|
+
# Step 2: Install agentic-devtools
|
|
532
|
+
if install_helpers:
|
|
533
|
+
result.helpers_installed = install_agentic_devtools(result.worktree_path)
|
|
534
|
+
|
|
535
|
+
# Step 3: Open VS Code
|
|
536
|
+
if open_vscode:
|
|
537
|
+
result.vscode_opened = open_vscode_workspace(result.worktree_path)
|
|
538
|
+
|
|
539
|
+
return result
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def check_worktree_exists(issue_key: str) -> Optional[str]:
|
|
543
|
+
"""
|
|
544
|
+
Check if a worktree for the given issue key already exists.
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
issue_key: The issue key to check for
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
The worktree path if it exists, None otherwise
|
|
551
|
+
"""
|
|
552
|
+
repos_parent = get_repos_parent_dir()
|
|
553
|
+
if not repos_parent:
|
|
554
|
+
return None
|
|
555
|
+
|
|
556
|
+
worktree_path = os.path.join(repos_parent, issue_key)
|
|
557
|
+
|
|
558
|
+
if os.path.exists(worktree_path):
|
|
559
|
+
# Verify it's a valid git worktree
|
|
560
|
+
git_file = os.path.join(worktree_path, ".git")
|
|
561
|
+
if os.path.exists(git_file):
|
|
562
|
+
return worktree_path
|
|
563
|
+
|
|
564
|
+
return None
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def get_worktree_continuation_prompt(
|
|
568
|
+
issue_key: str,
|
|
569
|
+
workflow_name: str,
|
|
570
|
+
user_request: Optional[str] = None,
|
|
571
|
+
additional_params: Optional[dict] = None,
|
|
572
|
+
) -> str:
|
|
573
|
+
"""
|
|
574
|
+
Generate a prompt for continuing a workflow in a new VS Code window.
|
|
575
|
+
|
|
576
|
+
This generates a copy/paste ready command that the user can paste into
|
|
577
|
+
the AI chat in the new VS Code window to continue the workflow.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
issue_key: The issue key
|
|
581
|
+
workflow_name: The workflow name (e.g., "work-on-jira-issue", "pull-request-review")
|
|
582
|
+
user_request: The user's explanation/request for what they want
|
|
583
|
+
(AI will use this to populate Jira fields appropriately)
|
|
584
|
+
additional_params: Additional parameters to include in the command
|
|
585
|
+
(e.g., {"pull_request_id": "12345"})
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
A formatted prompt string to paste in the new VS Code window
|
|
589
|
+
"""
|
|
590
|
+
# Build the base command for each workflow
|
|
591
|
+
workflow_base_commands = {
|
|
592
|
+
"work-on-jira-issue": "agdt-initiate-work-on-jira-issue-workflow",
|
|
593
|
+
"pull-request-review": "agdt-initiate-pull-request-review-workflow",
|
|
594
|
+
"create-jira-issue": "agdt-initiate-create-jira-issue-workflow",
|
|
595
|
+
"create-jira-epic": "agdt-initiate-create-jira-epic-workflow",
|
|
596
|
+
"create-jira-subtask": "agdt-initiate-create-jira-subtask-workflow",
|
|
597
|
+
"update-jira-issue": "agdt-initiate-update-jira-issue-workflow",
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
base_command = workflow_base_commands.get(workflow_name, "")
|
|
601
|
+
|
|
602
|
+
if not base_command:
|
|
603
|
+
return f"Continue working on issue {issue_key} in the new VS Code window."
|
|
604
|
+
|
|
605
|
+
# Build the full command with all parameters
|
|
606
|
+
command_parts = [base_command, f"--issue-key {issue_key}"]
|
|
607
|
+
|
|
608
|
+
# Add user-request if provided (for create workflows)
|
|
609
|
+
if user_request:
|
|
610
|
+
# Escape quotes in the value for shell safety
|
|
611
|
+
escaped_request = user_request.replace('"', '\\"')
|
|
612
|
+
command_parts.append(f'--user-request "{escaped_request}"')
|
|
613
|
+
|
|
614
|
+
# Add additional parameters if provided
|
|
615
|
+
if additional_params:
|
|
616
|
+
param_order = ["parent_key", "pull_request_id"]
|
|
617
|
+
for param_name in param_order:
|
|
618
|
+
if param_name in additional_params and additional_params[param_name]:
|
|
619
|
+
value = str(additional_params[param_name])
|
|
620
|
+
# Escape quotes in the value for shell safety
|
|
621
|
+
escaped_value = value.replace('"', '\\"')
|
|
622
|
+
cli_param = param_name.replace("_", "-")
|
|
623
|
+
command_parts.append(f'--{cli_param} "{escaped_value}"')
|
|
624
|
+
|
|
625
|
+
full_command = " ".join(command_parts)
|
|
626
|
+
|
|
627
|
+
# Generate a friendly description of what to do
|
|
628
|
+
return f"""
|
|
629
|
+
================================================================================
|
|
630
|
+
š COPY THE COMMAND BELOW INTO THE NEW VS CODE WINDOW
|
|
631
|
+
================================================================================
|
|
632
|
+
|
|
633
|
+
In the new VS Code window's AI chat (Copilot/Claude), paste this command:
|
|
634
|
+
|
|
635
|
+
```
|
|
636
|
+
{full_command}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
This will continue the {workflow_name} workflow with all the context preserved.
|
|
640
|
+
================================================================================"""
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def get_ai_agent_continuation_prompt(
|
|
644
|
+
issue_key: str,
|
|
645
|
+
workflow_name: str = "work-on-jira-issue",
|
|
646
|
+
user_request: Optional[str] = None,
|
|
647
|
+
additional_params: Optional[dict] = None,
|
|
648
|
+
) -> str:
|
|
649
|
+
"""
|
|
650
|
+
Generate a detailed prompt for AI agents to continue working on an issue.
|
|
651
|
+
|
|
652
|
+
This is used when a new VS Code window is opened in a worktree to provide
|
|
653
|
+
the AI agent with clear instructions on how to proceed.
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
issue_key: The Jira issue key (e.g., "DFLY-1234") or PR identifier (e.g., "PR24031")
|
|
657
|
+
workflow_name: The workflow being executed (e.g., "update-jira-issue")
|
|
658
|
+
user_request: The user's request/explanation for the workflow
|
|
659
|
+
additional_params: Additional parameters for the command (e.g., {"pull_request_id": "24031"})
|
|
660
|
+
|
|
661
|
+
Returns:
|
|
662
|
+
A detailed prompt string formatted for AI agents
|
|
663
|
+
"""
|
|
664
|
+
# Build the base command for each workflow
|
|
665
|
+
workflow_base_commands = {
|
|
666
|
+
"work-on-jira-issue": "agdt-initiate-work-on-jira-issue-workflow",
|
|
667
|
+
"pull-request-review": "agdt-initiate-pull-request-review-workflow",
|
|
668
|
+
"create-jira-issue": "agdt-initiate-create-jira-issue-workflow",
|
|
669
|
+
"create-jira-epic": "agdt-initiate-create-jira-epic-workflow",
|
|
670
|
+
"create-jira-subtask": "agdt-initiate-create-jira-subtask-workflow",
|
|
671
|
+
"update-jira-issue": "agdt-initiate-update-jira-issue-workflow",
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
base_command = workflow_base_commands.get(workflow_name, "agdt-initiate-work-on-jira-issue-workflow")
|
|
675
|
+
|
|
676
|
+
# Build the full command with parameters
|
|
677
|
+
# For pull-request-review, use --pull-request-id instead of --issue-key
|
|
678
|
+
if workflow_name == "pull-request-review" and additional_params and additional_params.get("pull_request_id"):
|
|
679
|
+
pull_request_id = additional_params["pull_request_id"]
|
|
680
|
+
command_parts = [base_command, f"--pull-request-id {pull_request_id}"]
|
|
681
|
+
else:
|
|
682
|
+
command_parts = [base_command, f"--issue-key {issue_key}"]
|
|
683
|
+
|
|
684
|
+
if user_request:
|
|
685
|
+
# Escape quotes for shell safety
|
|
686
|
+
escaped_request = user_request.replace('"', '\\"')
|
|
687
|
+
command_parts.append(f'--user-request "{escaped_request}"')
|
|
688
|
+
|
|
689
|
+
full_command = " ".join(command_parts)
|
|
690
|
+
|
|
691
|
+
# Generate workflow-appropriate prompt text
|
|
692
|
+
if workflow_name == "update-jira-issue":
|
|
693
|
+
task_description = "assigned to update a Jira issue's metadata"
|
|
694
|
+
action_description = (
|
|
695
|
+
"update the Jira issue fields (summary, description, acceptance criteria) as specified in the user request"
|
|
696
|
+
)
|
|
697
|
+
elif workflow_name in ("create-jira-issue", "create-jira-epic", "create-jira-subtask"):
|
|
698
|
+
task_description = "assigned to create a new Jira issue"
|
|
699
|
+
action_description = (
|
|
700
|
+
"populate the placeholder Jira issue with proper summary, description, "
|
|
701
|
+
"and acceptance criteria based on the user request"
|
|
702
|
+
)
|
|
703
|
+
elif workflow_name == "pull-request-review":
|
|
704
|
+
task_description = "assigned to review a pull request"
|
|
705
|
+
action_description = "review the pull request thoroughly and provide feedback"
|
|
706
|
+
else:
|
|
707
|
+
task_description = "assigned an issue to work on"
|
|
708
|
+
action_description = "work on the issue until you have completed the workflow"
|
|
709
|
+
|
|
710
|
+
return f"""You are a senior software engineer and expert architect who has been {task_description}.
|
|
711
|
+
|
|
712
|
+
Please run the following command:
|
|
713
|
+
|
|
714
|
+
{full_command}
|
|
715
|
+
|
|
716
|
+
to initiate the workflow and then follow the instructions logged to the console to {action_description}.
|
|
717
|
+
|
|
718
|
+
Work as independently as possible, only pausing to ask questions or seek approval if absolutely \
|
|
719
|
+
necessary. As a senior software engineer and expert architect you don't want or need individual \
|
|
720
|
+
approval for every command that you execute, so use the example commands which can be auto approved \
|
|
721
|
+
and you will be able to develop a quality solution much more efficiently.
|
|
722
|
+
|
|
723
|
+
It is anyway not sensible to ask questions or ask for approval, because once your work is complete \
|
|
724
|
+
another senior software engineer and expert architect in your team will thoroughly review your work. \
|
|
725
|
+
So work through the entire process to the best of your abilities knowing that a trusted colleague \
|
|
726
|
+
will review it all thoroughly and provide feedback at that time if necessary."""
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def setup_worktree_in_background_sync(
|
|
730
|
+
issue_key: str,
|
|
731
|
+
branch_prefix: str = "feature",
|
|
732
|
+
branch_name: Optional[str] = None,
|
|
733
|
+
use_existing_branch: bool = False,
|
|
734
|
+
workflow_name: str = "work-on-jira-issue",
|
|
735
|
+
user_request: Optional[str] = None,
|
|
736
|
+
additional_params: Optional[dict] = None,
|
|
737
|
+
) -> None:
|
|
738
|
+
"""
|
|
739
|
+
Perform worktree setup synchronously (called from background task).
|
|
740
|
+
|
|
741
|
+
This function is designed to be called from a background task runner.
|
|
742
|
+
It performs the full worktree setup and prints the continuation prompt.
|
|
743
|
+
|
|
744
|
+
Args:
|
|
745
|
+
issue_key: The Jira issue key
|
|
746
|
+
branch_prefix: Prefix for the branch name (default: "feature").
|
|
747
|
+
Ignored if branch_name is provided.
|
|
748
|
+
branch_name: Exact branch name to use. If provided, branch_prefix is ignored.
|
|
749
|
+
Used for PR review workflows where the branch already exists on origin.
|
|
750
|
+
use_existing_branch: If True and branch_name is provided, checkout the
|
|
751
|
+
existing branch from origin instead of creating a new one.
|
|
752
|
+
workflow_name: The workflow name for continuation prompt
|
|
753
|
+
user_request: The user's explanation of what they want
|
|
754
|
+
additional_params: Additional parameters for continuation command
|
|
755
|
+
"""
|
|
756
|
+
print(f"\n{'=' * 80}")
|
|
757
|
+
print("BACKGROUND WORKTREE SETUP")
|
|
758
|
+
print("=" * 80)
|
|
759
|
+
|
|
760
|
+
# Check if worktree already exists
|
|
761
|
+
existing_path = check_worktree_exists(issue_key)
|
|
762
|
+
if existing_path:
|
|
763
|
+
print(f"\nWorktree already exists at: {existing_path}")
|
|
764
|
+
print("Ensuring development tools are configured...")
|
|
765
|
+
|
|
766
|
+
# Ensure helpers are installed
|
|
767
|
+
helpers_installed = install_agentic_devtools(existing_path)
|
|
768
|
+
print(f" Helpers installed: {'Yes' if helpers_installed else 'No'}")
|
|
769
|
+
|
|
770
|
+
# Open VS Code
|
|
771
|
+
vscode_opened = open_vscode_workspace(existing_path)
|
|
772
|
+
print(f" VS Code opened: {'Yes' if vscode_opened else 'No'}")
|
|
773
|
+
|
|
774
|
+
print("\nā
Environment ready!")
|
|
775
|
+
print(get_worktree_continuation_prompt(issue_key, workflow_name, user_request, additional_params))
|
|
776
|
+
print("\n" + "=" * 80)
|
|
777
|
+
print("AI AGENT INSTRUCTIONS - IMPORTANT")
|
|
778
|
+
print("=" * 80)
|
|
779
|
+
print("""
|
|
780
|
+
Please provide the following prompt in your chat response to the user so they
|
|
781
|
+
can copy and paste it into the new VS Code window that just opened:
|
|
782
|
+
""")
|
|
783
|
+
print("--- BEGIN PROMPT FOR USER TO COPY ---")
|
|
784
|
+
print(get_ai_agent_continuation_prompt(issue_key, workflow_name, user_request, additional_params))
|
|
785
|
+
print("--- END PROMPT FOR USER TO COPY ---")
|
|
786
|
+
return
|
|
787
|
+
|
|
788
|
+
# Create new worktree environment
|
|
789
|
+
print(f"\nCreating worktree for issue {issue_key}...")
|
|
790
|
+
if use_existing_branch and branch_name:
|
|
791
|
+
print(f" Using existing branch from origin: {branch_name}")
|
|
792
|
+
|
|
793
|
+
result = setup_worktree_environment(
|
|
794
|
+
issue_key=issue_key,
|
|
795
|
+
branch_prefix=branch_prefix,
|
|
796
|
+
branch_name=branch_name,
|
|
797
|
+
use_existing_branch=use_existing_branch,
|
|
798
|
+
install_helpers=True,
|
|
799
|
+
open_vscode=True,
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
if result.success:
|
|
803
|
+
print("\nā
Environment setup complete!")
|
|
804
|
+
print(f" Worktree: {result.worktree_path}")
|
|
805
|
+
print(f" Branch: {result.branch_name}")
|
|
806
|
+
print(f" Helpers installed: {'Yes' if result.helpers_installed else 'No'}")
|
|
807
|
+
print(f" VS Code opened: {'Yes' if result.vscode_opened else 'No'}")
|
|
808
|
+
print(get_worktree_continuation_prompt(issue_key, workflow_name, user_request, additional_params))
|
|
809
|
+
print("\n" + "=" * 80)
|
|
810
|
+
print("AI AGENT INSTRUCTIONS - IMPORTANT")
|
|
811
|
+
print("=" * 80)
|
|
812
|
+
print("""
|
|
813
|
+
Please provide the following prompt in your chat response to the user so they
|
|
814
|
+
can copy and paste it into the new VS Code window that just opened:
|
|
815
|
+
""")
|
|
816
|
+
print("--- BEGIN PROMPT FOR USER TO COPY ---")
|
|
817
|
+
print(get_ai_agent_continuation_prompt(issue_key, workflow_name, user_request, additional_params))
|
|
818
|
+
print("--- END PROMPT FOR USER TO COPY ---")
|
|
819
|
+
else:
|
|
820
|
+
print(f"\nā Setup failed: {result.error_message}")
|
|
821
|
+
raise RuntimeError(f"Worktree setup failed: {result.error_message}")
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _setup_worktree_from_state() -> None:
|
|
825
|
+
"""
|
|
826
|
+
Wrapper function for background task execution.
|
|
827
|
+
|
|
828
|
+
This function is called dynamically by run_function_in_background
|
|
829
|
+
via string reference (see __all__ export at module top).
|
|
830
|
+
|
|
831
|
+
This reads parameters from state and calls setup_worktree_in_background_sync.
|
|
832
|
+
Used by run_function_in_background since it only supports parameterless functions.
|
|
833
|
+
"""
|
|
834
|
+
import json
|
|
835
|
+
|
|
836
|
+
from ...state import get_value
|
|
837
|
+
|
|
838
|
+
# Read parameters from state
|
|
839
|
+
issue_key = get_value("worktree_setup.issue_key")
|
|
840
|
+
branch_prefix = get_value("worktree_setup.branch_prefix") or "feature"
|
|
841
|
+
branch_name = get_value("worktree_setup.branch_name")
|
|
842
|
+
use_existing_branch = get_value("worktree_setup.use_existing_branch") == "true"
|
|
843
|
+
workflow_name = get_value("worktree_setup.workflow_name") or "work-on-jira-issue"
|
|
844
|
+
user_request = get_value("worktree_setup.user_request")
|
|
845
|
+
additional_params_str = get_value("worktree_setup.additional_params")
|
|
846
|
+
|
|
847
|
+
additional_params = None
|
|
848
|
+
if additional_params_str:
|
|
849
|
+
try:
|
|
850
|
+
additional_params = json.loads(additional_params_str)
|
|
851
|
+
except json.JSONDecodeError:
|
|
852
|
+
pass
|
|
853
|
+
|
|
854
|
+
if not issue_key:
|
|
855
|
+
raise ValueError("worktree_setup.issue_key not set in state")
|
|
856
|
+
|
|
857
|
+
# Call the actual setup function
|
|
858
|
+
setup_worktree_in_background_sync(
|
|
859
|
+
issue_key=issue_key,
|
|
860
|
+
branch_prefix=branch_prefix,
|
|
861
|
+
branch_name=branch_name,
|
|
862
|
+
use_existing_branch=use_existing_branch,
|
|
863
|
+
workflow_name=workflow_name,
|
|
864
|
+
user_request=user_request,
|
|
865
|
+
additional_params=additional_params,
|
|
866
|
+
)
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def start_worktree_setup_background(
|
|
870
|
+
issue_key: str,
|
|
871
|
+
branch_prefix: str = "feature",
|
|
872
|
+
branch_name: Optional[str] = None,
|
|
873
|
+
use_existing_branch: bool = False,
|
|
874
|
+
workflow_name: str = "work-on-jira-issue",
|
|
875
|
+
user_request: Optional[str] = None,
|
|
876
|
+
additional_params: Optional[dict] = None,
|
|
877
|
+
) -> str:
|
|
878
|
+
"""
|
|
879
|
+
Start worktree setup as a background task.
|
|
880
|
+
|
|
881
|
+
This spawns a background process to create the worktree, install helpers,
|
|
882
|
+
and open VS Code. The calling process returns immediately, allowing the
|
|
883
|
+
command line to be available.
|
|
884
|
+
|
|
885
|
+
Args:
|
|
886
|
+
issue_key: The Jira issue key
|
|
887
|
+
branch_prefix: Prefix for the branch name (default: "feature").
|
|
888
|
+
Ignored if branch_name is provided.
|
|
889
|
+
branch_name: Exact branch name to use. If provided, branch_prefix is ignored.
|
|
890
|
+
Used for PR review workflows where the branch already exists on origin.
|
|
891
|
+
use_existing_branch: If True and branch_name is provided, checkout the
|
|
892
|
+
existing branch from origin instead of creating a new one.
|
|
893
|
+
workflow_name: The workflow name for continuation prompt
|
|
894
|
+
user_request: The user's explanation of what they want
|
|
895
|
+
additional_params: Additional parameters for continuation command
|
|
896
|
+
|
|
897
|
+
Returns:
|
|
898
|
+
The background task ID for tracking progress
|
|
899
|
+
"""
|
|
900
|
+
import json
|
|
901
|
+
|
|
902
|
+
from ...background_tasks import run_function_in_background
|
|
903
|
+
from ...state import set_value
|
|
904
|
+
|
|
905
|
+
# Store parameters in state for the background function to read
|
|
906
|
+
set_value("worktree_setup.issue_key", issue_key)
|
|
907
|
+
set_value("worktree_setup.branch_prefix", branch_prefix)
|
|
908
|
+
set_value("worktree_setup.workflow_name", workflow_name)
|
|
909
|
+
if branch_name:
|
|
910
|
+
set_value("worktree_setup.branch_name", branch_name)
|
|
911
|
+
if use_existing_branch:
|
|
912
|
+
set_value("worktree_setup.use_existing_branch", "true")
|
|
913
|
+
if user_request:
|
|
914
|
+
set_value("worktree_setup.user_request", user_request)
|
|
915
|
+
if additional_params:
|
|
916
|
+
set_value("worktree_setup.additional_params", json.dumps(additional_params))
|
|
917
|
+
|
|
918
|
+
# Build display name for the task
|
|
919
|
+
display_name = f"agdt-setup-worktree-background --issue-key {issue_key}"
|
|
920
|
+
|
|
921
|
+
# Start background task using function-based runner
|
|
922
|
+
# This avoids the need for global CLI commands to be installed
|
|
923
|
+
task = run_function_in_background(
|
|
924
|
+
module_path="agentic_devtools.cli.workflows.worktree_setup",
|
|
925
|
+
function_name="_setup_worktree_from_state",
|
|
926
|
+
command_display_name=display_name,
|
|
927
|
+
args={
|
|
928
|
+
"issue_key": issue_key,
|
|
929
|
+
"branch_prefix": branch_prefix,
|
|
930
|
+
"branch_name": branch_name,
|
|
931
|
+
"use_existing_branch": use_existing_branch,
|
|
932
|
+
"workflow_name": workflow_name,
|
|
933
|
+
},
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
return task.id
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
# =============================================================================
|
|
940
|
+
# Placeholder Issue Creation for Create Workflows
|
|
941
|
+
# =============================================================================
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
@dataclass
|
|
945
|
+
class PlaceholderIssueResult:
|
|
946
|
+
"""Result of placeholder issue creation."""
|
|
947
|
+
|
|
948
|
+
success: bool
|
|
949
|
+
issue_key: Optional[str] = None
|
|
950
|
+
error_message: Optional[str] = None
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
def create_placeholder_issue(
|
|
954
|
+
project_key: str,
|
|
955
|
+
issue_type: str = "Task",
|
|
956
|
+
parent_key: Optional[str] = None,
|
|
957
|
+
) -> PlaceholderIssueResult:
|
|
958
|
+
"""
|
|
959
|
+
Create a placeholder Jira issue with minimal fields.
|
|
960
|
+
|
|
961
|
+
This creates an issue with a placeholder summary and description
|
|
962
|
+
that will be updated later in the workflow.
|
|
963
|
+
|
|
964
|
+
Args:
|
|
965
|
+
project_key: Jira project key (e.g., "DFLY")
|
|
966
|
+
issue_type: Issue type (Task, Epic, Sub-task)
|
|
967
|
+
parent_key: Parent issue key (required for Sub-task type)
|
|
968
|
+
|
|
969
|
+
Returns:
|
|
970
|
+
PlaceholderIssueResult with success status and issue key
|
|
971
|
+
"""
|
|
972
|
+
try:
|
|
973
|
+
from ..jira.create_commands import create_issue_sync
|
|
974
|
+
|
|
975
|
+
# Generate placeholder values
|
|
976
|
+
placeholder_summary = f"[Placeholder] {issue_type} created via workflow"
|
|
977
|
+
placeholder_description = (
|
|
978
|
+
"This issue was created as a placeholder by the workflow automation.\n\n"
|
|
979
|
+
"Please update the summary, description, and other fields as needed."
|
|
980
|
+
)
|
|
981
|
+
placeholder_labels = ["workflow-placeholder"]
|
|
982
|
+
|
|
983
|
+
# For Epic, we need an epic name
|
|
984
|
+
epic_name = None
|
|
985
|
+
if issue_type.lower() == "epic":
|
|
986
|
+
epic_name = placeholder_summary
|
|
987
|
+
|
|
988
|
+
print(f"Creating placeholder {issue_type} in project {project_key}...")
|
|
989
|
+
|
|
990
|
+
result = create_issue_sync(
|
|
991
|
+
project_key=project_key,
|
|
992
|
+
summary=placeholder_summary,
|
|
993
|
+
issue_type=issue_type,
|
|
994
|
+
description=placeholder_description,
|
|
995
|
+
labels=placeholder_labels,
|
|
996
|
+
epic_name=epic_name,
|
|
997
|
+
parent_key=parent_key,
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
issue_key = result.get("key")
|
|
1001
|
+
if issue_key:
|
|
1002
|
+
print(f"ā
Placeholder {issue_type} created: {issue_key}")
|
|
1003
|
+
return PlaceholderIssueResult(success=True, issue_key=issue_key)
|
|
1004
|
+
else:
|
|
1005
|
+
return PlaceholderIssueResult(
|
|
1006
|
+
success=False,
|
|
1007
|
+
error_message="API did not return an issue key",
|
|
1008
|
+
)
|
|
1009
|
+
|
|
1010
|
+
except Exception as e:
|
|
1011
|
+
return PlaceholderIssueResult(
|
|
1012
|
+
success=False,
|
|
1013
|
+
error_message=str(e),
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def create_placeholder_and_setup_worktree(
|
|
1018
|
+
project_key: str,
|
|
1019
|
+
issue_type: str = "Task",
|
|
1020
|
+
parent_key: Optional[str] = None,
|
|
1021
|
+
workflow_name: str = "create-jira-issue",
|
|
1022
|
+
user_request: Optional[str] = None,
|
|
1023
|
+
additional_params: Optional[dict] = None,
|
|
1024
|
+
) -> Tuple[bool, Optional[str]]:
|
|
1025
|
+
"""
|
|
1026
|
+
Create a placeholder issue and set up a worktree for it.
|
|
1027
|
+
|
|
1028
|
+
This is the main entry point for create workflows that need both
|
|
1029
|
+
issue creation and environment setup.
|
|
1030
|
+
|
|
1031
|
+
Args:
|
|
1032
|
+
project_key: Jira project key (e.g., "DFLY")
|
|
1033
|
+
issue_type: Issue type (Task, Epic, Sub-task)
|
|
1034
|
+
parent_key: Parent issue key (required for Sub-task type)
|
|
1035
|
+
workflow_name: Name of the workflow for continuation prompt
|
|
1036
|
+
user_request: The user's explanation of what they want to create
|
|
1037
|
+
(AI will use this to populate Jira fields appropriately)
|
|
1038
|
+
additional_params: Additional parameters to include in the continuation
|
|
1039
|
+
command (e.g., {"parent_key": "DFLY-1234"})
|
|
1040
|
+
|
|
1041
|
+
Returns:
|
|
1042
|
+
Tuple of (success, issue_key). If success is True, issue_key contains
|
|
1043
|
+
the created issue key. If success is False, issue_key is None.
|
|
1044
|
+
"""
|
|
1045
|
+
print(f"\n{'=' * 80}")
|
|
1046
|
+
print(f"CREATE WORKFLOW: {workflow_name}")
|
|
1047
|
+
print("=" * 80)
|
|
1048
|
+
|
|
1049
|
+
# Step 1: Create placeholder issue
|
|
1050
|
+
print("\nš Step 1: Creating placeholder Jira issue...")
|
|
1051
|
+
issue_result = create_placeholder_issue(
|
|
1052
|
+
project_key=project_key,
|
|
1053
|
+
issue_type=issue_type,
|
|
1054
|
+
parent_key=parent_key,
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
if not issue_result.success:
|
|
1058
|
+
print(f"\nā Failed to create placeholder issue: {issue_result.error_message}")
|
|
1059
|
+
return False, None
|
|
1060
|
+
|
|
1061
|
+
issue_key = issue_result.issue_key
|
|
1062
|
+
print(f" Issue key: {issue_key}")
|
|
1063
|
+
|
|
1064
|
+
# Set the issue key in state for later use
|
|
1065
|
+
from ...state import set_value
|
|
1066
|
+
|
|
1067
|
+
set_value("jira.issue_key", issue_key)
|
|
1068
|
+
|
|
1069
|
+
# Step 2: Set up worktree environment
|
|
1070
|
+
print("\nš§ Step 2: Setting up worktree environment...")
|
|
1071
|
+
|
|
1072
|
+
# Check if worktree already exists (unlikely for new issue, but check anyway)
|
|
1073
|
+
existing_path = check_worktree_exists(issue_key)
|
|
1074
|
+
if existing_path:
|
|
1075
|
+
print(f" Worktree already exists at: {existing_path}")
|
|
1076
|
+
install_agentic_devtools(existing_path)
|
|
1077
|
+
open_vscode_workspace(existing_path)
|
|
1078
|
+
print(get_worktree_continuation_prompt(issue_key, workflow_name, user_request, additional_params))
|
|
1079
|
+
return True, issue_key
|
|
1080
|
+
|
|
1081
|
+
# Generate branch name based on issue type and workflow
|
|
1082
|
+
branch_name = generate_workflow_branch_name(
|
|
1083
|
+
issue_key=issue_key,
|
|
1084
|
+
issue_type=issue_type,
|
|
1085
|
+
workflow_name=workflow_name,
|
|
1086
|
+
parent_key=parent_key,
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
# Create new worktree with generated branch name
|
|
1090
|
+
result = setup_worktree_environment(
|
|
1091
|
+
issue_key=issue_key,
|
|
1092
|
+
branch_name=branch_name,
|
|
1093
|
+
install_helpers=True,
|
|
1094
|
+
open_vscode=True,
|
|
1095
|
+
)
|
|
1096
|
+
|
|
1097
|
+
if result.success:
|
|
1098
|
+
print("\nā
Environment setup complete!")
|
|
1099
|
+
print(f" Worktree: {result.worktree_path}")
|
|
1100
|
+
print(f" Branch: {result.branch_name}")
|
|
1101
|
+
print(f" Helpers installed: {'Yes' if result.helpers_installed else 'No'}")
|
|
1102
|
+
print(f" VS Code opened: {'Yes' if result.vscode_opened else 'No'}")
|
|
1103
|
+
print(get_worktree_continuation_prompt(issue_key, workflow_name, user_request, additional_params))
|
|
1104
|
+
return True, issue_key
|
|
1105
|
+
else:
|
|
1106
|
+
print(f"\nā Worktree setup failed: {result.error_message}")
|
|
1107
|
+
print(f" Issue {issue_key} was created but environment setup failed.")
|
|
1108
|
+
print(" Please set up the worktree manually:")
|
|
1109
|
+
print(f" git worktree add ../{issue_key} -b {branch_name}")
|
|
1110
|
+
return False, issue_key
|