adws 0.3.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.
- adws/__init__.py +19 -0
- adws/adw_plan_build.py +536 -0
- adws/agent.py +94 -0
- adws/cli.py +363 -0
- adws/data_types.py +154 -0
- adws/github.py +291 -0
- adws/health_check.py +392 -0
- adws/providers.py +468 -0
- adws/py.typed +0 -0
- adws/trigger_cron.py +224 -0
- adws/trigger_webhook.py +207 -0
- adws/utils.py +79 -0
- adws-0.3.0.dist-info/METADATA +406 -0
- adws-0.3.0.dist-info/RECORD +18 -0
- adws-0.3.0.dist-info/WHEEL +5 -0
- adws-0.3.0.dist-info/entry_points.txt +2 -0
- adws-0.3.0.dist-info/licenses/LICENSE +21 -0
- adws-0.3.0.dist-info/top_level.txt +1 -0
adws/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""ADW - AI Developer Workflow System.
|
|
2
|
+
|
|
3
|
+
Agentic Engineering Framework for automating issue classification,
|
|
4
|
+
planning, implementation, and PR creation.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from adws.github import fetch_issue, make_issue_comment
|
|
8
|
+
from adws.providers import get_provider
|
|
9
|
+
from adws.agent import execute_with_provider
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "0.3.0"
|
|
13
|
+
__all__ = [
|
|
14
|
+
"agent",
|
|
15
|
+
"data_types",
|
|
16
|
+
"github",
|
|
17
|
+
"providers",
|
|
18
|
+
"utils",
|
|
19
|
+
]
|
adws/adw_plan_build.py
ADDED
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
#!/usr/bin/env -S uv run
|
|
2
|
+
# /// script
|
|
3
|
+
# dependencies = ["python-dotenv", "pydantic"]
|
|
4
|
+
# ///
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
ADW Plan & Build - AI Developer Workflow (ADW)
|
|
8
|
+
|
|
9
|
+
Usage: uv run adw_plan_build.py <github-issue-number> [adw-id]
|
|
10
|
+
|
|
11
|
+
Workflow:
|
|
12
|
+
1. Fetch GitHub issue details
|
|
13
|
+
2. Create feature branch: feature/issue-{number}-{slug}
|
|
14
|
+
3. Plan Agent: Generate implementation plan
|
|
15
|
+
- Prompt Claude Code with issue context
|
|
16
|
+
- Comment plan on issue
|
|
17
|
+
- Commit: "chore: add implementation plan for #{number}"
|
|
18
|
+
4. Build Agent: Implement the solution
|
|
19
|
+
- Prompt Claude Code with plan + codebase context
|
|
20
|
+
- Comment implementation summary on issue
|
|
21
|
+
- Commit: "feature: implement #{number} - {title}"
|
|
22
|
+
5. Create PR with full context
|
|
23
|
+
|
|
24
|
+
Environment Requirements:
|
|
25
|
+
- ANTHROPIC_API_KEY: Anthropic API key
|
|
26
|
+
- CLAUDE_CODE_PATH: Path to Claude CLI
|
|
27
|
+
- GITHUB_PAT: (Optional) GitHub Personal Access Token - only if using a different account than 'gh auth login'
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import subprocess
|
|
31
|
+
import sys
|
|
32
|
+
import os
|
|
33
|
+
import logging
|
|
34
|
+
from typing import Tuple, Optional, Union
|
|
35
|
+
from dotenv import load_dotenv
|
|
36
|
+
from data_types import (
|
|
37
|
+
AgentTemplateRequest,
|
|
38
|
+
GitHubIssue,
|
|
39
|
+
AgentPromptResponse,
|
|
40
|
+
IssueClassSlashCommand,
|
|
41
|
+
)
|
|
42
|
+
from agent import execute_template
|
|
43
|
+
from github import (
|
|
44
|
+
extract_repo_path,
|
|
45
|
+
fetch_issue,
|
|
46
|
+
make_issue_comment,
|
|
47
|
+
mark_issue_in_progress,
|
|
48
|
+
get_repo_url,
|
|
49
|
+
)
|
|
50
|
+
from utils import make_adw_id, setup_logger
|
|
51
|
+
|
|
52
|
+
# Agent name constants
|
|
53
|
+
AGENT_PLANNER = "sdlc_planner"
|
|
54
|
+
AGENT_IMPLEMENTOR = "sdlc_implementor"
|
|
55
|
+
AGENT_CLASSIFIER = "issue_classifier"
|
|
56
|
+
AGENT_PLAN_FINDER = "plan_finder"
|
|
57
|
+
AGENT_BRANCH_GENERATOR = "branch_generator"
|
|
58
|
+
AGENT_PR_CREATOR = "pr_creator"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def check_env_vars(logger: Optional[logging.Logger] = None) -> None:
|
|
62
|
+
"""Check that all required environment variables are set."""
|
|
63
|
+
required_vars = [
|
|
64
|
+
"ANTHROPIC_API_KEY",
|
|
65
|
+
"CLAUDE_CODE_PATH",
|
|
66
|
+
]
|
|
67
|
+
missing_vars = [var for var in required_vars if not os.getenv(var)]
|
|
68
|
+
|
|
69
|
+
if missing_vars:
|
|
70
|
+
error_msg = "Error: Missing required environment variables:"
|
|
71
|
+
if logger:
|
|
72
|
+
logger.error(error_msg)
|
|
73
|
+
for var in missing_vars:
|
|
74
|
+
logger.error(f" - {var}")
|
|
75
|
+
else:
|
|
76
|
+
print(error_msg, file=sys.stderr)
|
|
77
|
+
for var in missing_vars:
|
|
78
|
+
print(f" - {var}", file=sys.stderr)
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def parse_args(logger: Optional[logging.Logger] = None) -> Tuple[str, Optional[str]]:
|
|
83
|
+
"""Parse command line arguments.
|
|
84
|
+
Returns (issue_number, adw_id) where adw_id may be None."""
|
|
85
|
+
if len(sys.argv) < 2:
|
|
86
|
+
usage_msg = [
|
|
87
|
+
"Usage: uv run adw_plan_build.py <issue-number> [adw-id]",
|
|
88
|
+
"Example: uv run adw_plan_build.py 123",
|
|
89
|
+
"Example: uv run adw_plan_build.py 123 abc12345",
|
|
90
|
+
]
|
|
91
|
+
if logger:
|
|
92
|
+
for msg in usage_msg:
|
|
93
|
+
logger.error(msg)
|
|
94
|
+
else:
|
|
95
|
+
for msg in usage_msg:
|
|
96
|
+
print(msg)
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
|
|
99
|
+
issue_number = sys.argv[1]
|
|
100
|
+
adw_id = sys.argv[2] if len(sys.argv) > 2 else None
|
|
101
|
+
|
|
102
|
+
return issue_number, adw_id
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def format_issue_message(
|
|
106
|
+
adw_id: str, agent_name: str, message: str, session_id: Optional[str] = None
|
|
107
|
+
) -> str:
|
|
108
|
+
"""Format a message for issue comments with ADW tracking."""
|
|
109
|
+
if session_id:
|
|
110
|
+
return f"{adw_id}_{agent_name}_{session_id}: {message}"
|
|
111
|
+
return f"{adw_id}_{agent_name}: {message}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def classify_issue(
|
|
115
|
+
issue: GitHubIssue, adw_id: str, logger: logging.Logger
|
|
116
|
+
) -> Tuple[Optional[IssueClassSlashCommand], Optional[str]]:
|
|
117
|
+
"""Classify GitHub issue and return appropriate slash command.
|
|
118
|
+
Returns (command, error_message) tuple."""
|
|
119
|
+
issue_template_request = AgentTemplateRequest(
|
|
120
|
+
agent_name=AGENT_CLASSIFIER,
|
|
121
|
+
slash_command="/classify_issue",
|
|
122
|
+
args=[issue.model_dump_json(indent=2, by_alias=True)],
|
|
123
|
+
adw_id=adw_id,
|
|
124
|
+
model="sonnet",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
logger.debug(
|
|
128
|
+
f"issue_template_request: {issue_template_request.model_dump_json(indent=2, by_alias=True)}"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
issue_response = execute_template(issue_template_request)
|
|
132
|
+
|
|
133
|
+
logger.debug(
|
|
134
|
+
f"issue_response: {issue_response.model_dump_json(indent=2, by_alias=True)}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if not issue_response.success:
|
|
138
|
+
return None, issue_response.output
|
|
139
|
+
|
|
140
|
+
issue_command = issue_response.output.strip()
|
|
141
|
+
|
|
142
|
+
if issue_command == "0":
|
|
143
|
+
return None, f"No command selected: {issue_response.output}"
|
|
144
|
+
|
|
145
|
+
if issue_command not in ["/chore", "/bug", "/feature"]:
|
|
146
|
+
return None, f"Invalid command selected: {issue_response.output}"
|
|
147
|
+
|
|
148
|
+
return issue_command, None # type: ignore
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def build_plan(
|
|
152
|
+
issue: GitHubIssue, command: str, adw_id: str, logger: logging.Logger
|
|
153
|
+
) -> AgentPromptResponse:
|
|
154
|
+
"""Build implementation plan for the issue using the specified command."""
|
|
155
|
+
issue_plan_template_request = AgentTemplateRequest(
|
|
156
|
+
agent_name=AGENT_PLANNER,
|
|
157
|
+
slash_command=command,
|
|
158
|
+
args=[issue.title + ": " + issue.body],
|
|
159
|
+
adw_id=adw_id,
|
|
160
|
+
model="sonnet",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
logger.debug(
|
|
164
|
+
f"issue_plan_template_request: {issue_plan_template_request.model_dump_json(indent=2, by_alias=True)}"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
issue_plan_response = execute_template(issue_plan_template_request)
|
|
168
|
+
|
|
169
|
+
logger.debug(
|
|
170
|
+
f"issue_plan_response: {issue_plan_response.model_dump_json(indent=2, by_alias=True)}"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return issue_plan_response
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def get_plan_file(
|
|
177
|
+
plan_output: str, adw_id: str, logger: logging.Logger
|
|
178
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
179
|
+
"""Get the path to the plan file that was just created.
|
|
180
|
+
Returns (file_path, error_message) tuple."""
|
|
181
|
+
request = AgentTemplateRequest(
|
|
182
|
+
agent_name=AGENT_PLAN_FINDER,
|
|
183
|
+
slash_command="/find_plan_file",
|
|
184
|
+
args=[plan_output],
|
|
185
|
+
adw_id=adw_id,
|
|
186
|
+
model="sonnet",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
response = execute_template(request)
|
|
190
|
+
|
|
191
|
+
if not response.success:
|
|
192
|
+
return None, response.output
|
|
193
|
+
|
|
194
|
+
# Clean up the response - get just the file path
|
|
195
|
+
file_path = response.output.strip()
|
|
196
|
+
|
|
197
|
+
# Validate it looks like a file path
|
|
198
|
+
if file_path and file_path != "0" and "/" in file_path:
|
|
199
|
+
return file_path, None
|
|
200
|
+
elif file_path == "0":
|
|
201
|
+
return None, "No plan file found in output"
|
|
202
|
+
else:
|
|
203
|
+
# If response doesn't look like a path, return error
|
|
204
|
+
return None, f"Invalid file path response: {file_path}"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def implement_plan(
|
|
208
|
+
plan_file: str, adw_id: str, logger: logging.Logger
|
|
209
|
+
) -> AgentPromptResponse:
|
|
210
|
+
"""Implement the plan using the /implement command."""
|
|
211
|
+
implement_template_request = AgentTemplateRequest(
|
|
212
|
+
agent_name=AGENT_IMPLEMENTOR,
|
|
213
|
+
slash_command="/implement",
|
|
214
|
+
args=[plan_file],
|
|
215
|
+
adw_id=adw_id,
|
|
216
|
+
model="sonnet",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
logger.debug(
|
|
220
|
+
f"implement_template_request: {implement_template_request.model_dump_json(indent=2, by_alias=True)}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
implement_response = execute_template(implement_template_request)
|
|
224
|
+
|
|
225
|
+
logger.debug(
|
|
226
|
+
f"implement_response: {implement_response.model_dump_json(indent=2, by_alias=True)}"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return implement_response
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def git_branch(
|
|
233
|
+
issue: GitHubIssue,
|
|
234
|
+
issue_class: IssueClassSlashCommand,
|
|
235
|
+
adw_id: str,
|
|
236
|
+
logger: logging.Logger,
|
|
237
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
238
|
+
"""Generate and create a git branch for the issue.
|
|
239
|
+
Returns (branch_name, error_message) tuple."""
|
|
240
|
+
# Remove the leading slash from issue_class for the branch name
|
|
241
|
+
issue_type = issue_class.replace("/", "")
|
|
242
|
+
|
|
243
|
+
request = AgentTemplateRequest(
|
|
244
|
+
agent_name=AGENT_BRANCH_GENERATOR,
|
|
245
|
+
slash_command="/generate_branch_name",
|
|
246
|
+
args=[issue_type, adw_id, issue.model_dump_json(by_alias=True)],
|
|
247
|
+
adw_id=adw_id,
|
|
248
|
+
model="sonnet",
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
response = execute_template(request)
|
|
252
|
+
|
|
253
|
+
if not response.success:
|
|
254
|
+
return None, response.output
|
|
255
|
+
|
|
256
|
+
branch_name = response.output.strip()
|
|
257
|
+
logger.info(f"Created branch: {branch_name}")
|
|
258
|
+
return branch_name, None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def git_commit(
|
|
262
|
+
agent_name: str,
|
|
263
|
+
issue: GitHubIssue,
|
|
264
|
+
issue_class: IssueClassSlashCommand,
|
|
265
|
+
adw_id: str,
|
|
266
|
+
logger: logging.Logger,
|
|
267
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
268
|
+
"""Create a git commit with a properly formatted message.
|
|
269
|
+
Returns (commit_message, error_message) tuple."""
|
|
270
|
+
# Remove the leading slash from issue_class
|
|
271
|
+
issue_type = issue_class.replace("/", "")
|
|
272
|
+
|
|
273
|
+
# Create unique committer agent name by suffixing '_committer'
|
|
274
|
+
unique_agent_name = f"{agent_name}_committer"
|
|
275
|
+
|
|
276
|
+
request = AgentTemplateRequest(
|
|
277
|
+
agent_name=unique_agent_name,
|
|
278
|
+
slash_command="/commit",
|
|
279
|
+
args=[agent_name, issue_type, issue.model_dump_json(by_alias=True)],
|
|
280
|
+
adw_id=adw_id,
|
|
281
|
+
model="sonnet",
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
response = execute_template(request)
|
|
285
|
+
|
|
286
|
+
if not response.success:
|
|
287
|
+
return None, response.output
|
|
288
|
+
|
|
289
|
+
commit_message = response.output.strip()
|
|
290
|
+
logger.info(f"Created commit: {commit_message}")
|
|
291
|
+
return commit_message, None
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def pull_request(
|
|
295
|
+
branch_name: str,
|
|
296
|
+
issue: GitHubIssue,
|
|
297
|
+
plan_file: str,
|
|
298
|
+
adw_id: str,
|
|
299
|
+
logger: logging.Logger,
|
|
300
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
301
|
+
"""Create a pull request for the implemented changes.
|
|
302
|
+
Returns (pr_url, error_message) tuple."""
|
|
303
|
+
request = AgentTemplateRequest(
|
|
304
|
+
agent_name=AGENT_PR_CREATOR,
|
|
305
|
+
slash_command="/pull_request",
|
|
306
|
+
args=[branch_name, issue.model_dump_json(by_alias=True), plan_file, adw_id],
|
|
307
|
+
adw_id=adw_id,
|
|
308
|
+
model="sonnet",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
response = execute_template(request)
|
|
312
|
+
|
|
313
|
+
if not response.success:
|
|
314
|
+
return None, response.output
|
|
315
|
+
|
|
316
|
+
pr_url = response.output.strip()
|
|
317
|
+
logger.info(f"Created pull request: {pr_url}")
|
|
318
|
+
return pr_url, None
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def check_error(
|
|
322
|
+
error_or_response: Union[Optional[str], AgentPromptResponse],
|
|
323
|
+
issue_number: str,
|
|
324
|
+
adw_id: str,
|
|
325
|
+
agent_name: str,
|
|
326
|
+
error_prefix: str,
|
|
327
|
+
logger: logging.Logger,
|
|
328
|
+
) -> None:
|
|
329
|
+
"""Check for errors and handle them uniformly.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
error_or_response: Either an error string or an AgentPromptResponse
|
|
333
|
+
issue_number: GitHub issue number
|
|
334
|
+
adw_id: ADW workflow ID
|
|
335
|
+
agent_name: Name of the agent
|
|
336
|
+
error_prefix: Prefix for error message (e.g., "Error creating branch")
|
|
337
|
+
logger: Logger instance
|
|
338
|
+
"""
|
|
339
|
+
error = None
|
|
340
|
+
|
|
341
|
+
# Handle AgentPromptResponse
|
|
342
|
+
if isinstance(error_or_response, AgentPromptResponse):
|
|
343
|
+
if not error_or_response.success:
|
|
344
|
+
error = error_or_response.output
|
|
345
|
+
else:
|
|
346
|
+
# Handle string error
|
|
347
|
+
error = error_or_response
|
|
348
|
+
|
|
349
|
+
if error:
|
|
350
|
+
logger.error(f"{error_prefix}: {error}")
|
|
351
|
+
make_issue_comment(
|
|
352
|
+
issue_number,
|
|
353
|
+
format_issue_message(adw_id, agent_name, f"❌ {error_prefix}: {error}"),
|
|
354
|
+
)
|
|
355
|
+
sys.exit(1)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def main():
|
|
359
|
+
"""Main entry point."""
|
|
360
|
+
# Load environment variables
|
|
361
|
+
load_dotenv()
|
|
362
|
+
|
|
363
|
+
# Parse arguments (before we have logger)
|
|
364
|
+
issue_number, adw_id = parse_args()
|
|
365
|
+
|
|
366
|
+
# Generate ADW ID if not provided
|
|
367
|
+
if not adw_id:
|
|
368
|
+
adw_id = make_adw_id()
|
|
369
|
+
|
|
370
|
+
# Set up logger with ADW ID
|
|
371
|
+
logger = setup_logger(adw_id, "adw_plan_build")
|
|
372
|
+
logger.info(f"ADW ID: {adw_id}")
|
|
373
|
+
|
|
374
|
+
# Validate environment (now with logger)
|
|
375
|
+
check_env_vars(logger)
|
|
376
|
+
|
|
377
|
+
# Get repo information from git remote
|
|
378
|
+
try:
|
|
379
|
+
github_repo_url: str = get_repo_url()
|
|
380
|
+
repo_path: str = extract_repo_path(github_repo_url)
|
|
381
|
+
except ValueError as e:
|
|
382
|
+
logger.error(f"Error getting repository URL: {e}")
|
|
383
|
+
sys.exit(1)
|
|
384
|
+
|
|
385
|
+
# Fetch and display issue
|
|
386
|
+
issue: GitHubIssue = fetch_issue(issue_number, repo_path)
|
|
387
|
+
|
|
388
|
+
logger.debug(f"issue: {issue.model_dump_json(indent=2, by_alias=True)}")
|
|
389
|
+
make_issue_comment(
|
|
390
|
+
issue_number, format_issue_message(adw_id, "ops", f"✅ Starting ADW workflow")
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Classify the issue
|
|
394
|
+
issue_command: IssueClassSlashCommand
|
|
395
|
+
issue_command, error = classify_issue(issue, adw_id, logger)
|
|
396
|
+
|
|
397
|
+
check_error(error, issue_number, adw_id, "ops", "Error classifying issue", logger)
|
|
398
|
+
|
|
399
|
+
logger.info(f"issue_command: {issue_command}")
|
|
400
|
+
make_issue_comment(
|
|
401
|
+
issue_number,
|
|
402
|
+
format_issue_message(adw_id, "ops", f"✅ Issue classified as: {issue_command}"),
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Create git branch
|
|
406
|
+
branch_name, error = git_branch(issue, issue_command, adw_id, logger)
|
|
407
|
+
|
|
408
|
+
check_error(error, issue_number, adw_id, "ops", "Error creating branch", logger)
|
|
409
|
+
|
|
410
|
+
logger.info(f"Working on branch: {branch_name}")
|
|
411
|
+
make_issue_comment(
|
|
412
|
+
issue_number,
|
|
413
|
+
format_issue_message(adw_id, "ops", f"✅ Working on branch: {branch_name}"),
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Build the implementation plan
|
|
417
|
+
logger.info("\n=== Building implementation plan ===")
|
|
418
|
+
make_issue_comment(
|
|
419
|
+
issue_number,
|
|
420
|
+
format_issue_message(adw_id, AGENT_PLANNER, "✅ Building implementation plan"),
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
issue_plan_response: AgentPromptResponse = build_plan(
|
|
424
|
+
issue, issue_command, adw_id, logger
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
check_error(
|
|
428
|
+
issue_plan_response,
|
|
429
|
+
issue_number,
|
|
430
|
+
adw_id,
|
|
431
|
+
AGENT_PLANNER,
|
|
432
|
+
"Error building plan",
|
|
433
|
+
logger,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
logger.debug(f"issue_plan_response.output: {issue_plan_response.output}")
|
|
437
|
+
make_issue_comment(
|
|
438
|
+
issue_number,
|
|
439
|
+
format_issue_message(adw_id, AGENT_PLANNER, "✅ Implementation plan created"),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Get the path to the plan file that was created
|
|
443
|
+
logger.info("\n=== Finding plan file ===")
|
|
444
|
+
|
|
445
|
+
plan_file_path, error = get_plan_file(issue_plan_response.output, adw_id, logger)
|
|
446
|
+
|
|
447
|
+
check_error(error, issue_number, adw_id, "ops", "Error finding plan file", logger)
|
|
448
|
+
|
|
449
|
+
logger.info(f"plan_file_path: {plan_file_path}")
|
|
450
|
+
make_issue_comment(
|
|
451
|
+
issue_number,
|
|
452
|
+
format_issue_message(adw_id, "ops", f"✅ Plan file created: {plan_file_path}"),
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
# Commit the plan
|
|
456
|
+
logger.info("\n=== Committing plan ===")
|
|
457
|
+
make_issue_comment(
|
|
458
|
+
issue_number, format_issue_message(adw_id, AGENT_PLANNER, "✅ Committing plan")
|
|
459
|
+
)
|
|
460
|
+
commit_msg, error = git_commit(AGENT_PLANNER, issue, issue_command, adw_id, logger)
|
|
461
|
+
|
|
462
|
+
check_error(
|
|
463
|
+
error, issue_number, adw_id, AGENT_PLANNER, "Error committing plan", logger
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# Implement the plan
|
|
467
|
+
logger.info("\n=== Implementing solution ===")
|
|
468
|
+
make_issue_comment(
|
|
469
|
+
issue_number,
|
|
470
|
+
format_issue_message(adw_id, AGENT_IMPLEMENTOR, "✅ Implementing solution"),
|
|
471
|
+
)
|
|
472
|
+
implement_response: AgentPromptResponse = implement_plan(
|
|
473
|
+
plan_file_path, adw_id, logger
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
check_error(
|
|
477
|
+
implement_response,
|
|
478
|
+
issue_number,
|
|
479
|
+
adw_id,
|
|
480
|
+
AGENT_IMPLEMENTOR,
|
|
481
|
+
"Error implementing solution",
|
|
482
|
+
logger,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
logger.debug(f"implement_response.output: {implement_response.output}")
|
|
486
|
+
make_issue_comment(
|
|
487
|
+
issue_number,
|
|
488
|
+
format_issue_message(adw_id, AGENT_IMPLEMENTOR, "✅ Solution implemented"),
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
# Commit the implementation
|
|
492
|
+
logger.info("\n=== Committing implementation ===")
|
|
493
|
+
make_issue_comment(
|
|
494
|
+
issue_number,
|
|
495
|
+
format_issue_message(adw_id, AGENT_IMPLEMENTOR, "✅ Committing implementation"),
|
|
496
|
+
)
|
|
497
|
+
commit_msg, error = git_commit(
|
|
498
|
+
AGENT_IMPLEMENTOR, issue, issue_command, adw_id, logger
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
check_error(
|
|
502
|
+
error,
|
|
503
|
+
issue_number,
|
|
504
|
+
adw_id,
|
|
505
|
+
AGENT_IMPLEMENTOR,
|
|
506
|
+
"Error committing implementation",
|
|
507
|
+
logger,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Create pull request
|
|
511
|
+
logger.info("\n=== Creating pull request ===")
|
|
512
|
+
make_issue_comment(
|
|
513
|
+
issue_number, format_issue_message(adw_id, "ops", "✅ Creating pull request")
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
pr_url, error = pull_request(branch_name, issue, plan_file_path, adw_id, logger)
|
|
517
|
+
|
|
518
|
+
check_error(
|
|
519
|
+
error, issue_number, adw_id, "ops", "Error creating pull request", logger
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
logger.info(f"\nPull request created: {pr_url}")
|
|
523
|
+
make_issue_comment(
|
|
524
|
+
issue_number,
|
|
525
|
+
format_issue_message(adw_id, "ops", f"✅ Pull request created: {pr_url}"),
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
logger.info(f"ADW workflow completed successfully for issue #{issue_number}")
|
|
529
|
+
make_issue_comment(
|
|
530
|
+
issue_number,
|
|
531
|
+
format_issue_message(adw_id, "ops", f"✅ ADW workflow completed successfully"),
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
if __name__ == "__main__":
|
|
536
|
+
main()
|
adws/agent.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Agent module for executing prompts via configurable agent providers.
|
|
2
|
+
|
|
3
|
+
Supported providers (set via ADW_AGENT_PROVIDER env var):
|
|
4
|
+
- claude_cli (default): Claude Code CLI
|
|
5
|
+
- opencode: OpenCode CLI
|
|
6
|
+
- venice_api: Venice.AI API (OpenAI-compatible)
|
|
7
|
+
|
|
8
|
+
All provider logic lives in providers.py. This module provides the
|
|
9
|
+
backward-compatible interface used by the rest of the ADW system.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from dotenv import load_dotenv
|
|
16
|
+
|
|
17
|
+
from data_types import AgentPromptRequest, AgentPromptResponse, AgentTemplateRequest
|
|
18
|
+
from providers import get_provider, AgentProvider, ClaudeCLIProvider, PROVIDERS
|
|
19
|
+
|
|
20
|
+
load_dotenv()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_current_provider() -> AgentProvider:
|
|
24
|
+
"""Get the currently configured agent provider."""
|
|
25
|
+
return get_provider()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def execute_with_provider(request: AgentPromptRequest) -> AgentPromptResponse:
|
|
29
|
+
"""Execute a prompt using the configured agent provider."""
|
|
30
|
+
provider = get_current_provider()
|
|
31
|
+
return provider.execute_prompt(request)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def execute_template_with_provider(request: AgentTemplateRequest) -> AgentPromptResponse:
|
|
35
|
+
"""Execute a template (slash command + args) using the configured agent provider."""
|
|
36
|
+
prompt = f"{request.slash_command} {' '.join(request.args)}"
|
|
37
|
+
|
|
38
|
+
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
39
|
+
output_dir = os.path.join(project_root, "agents", request.adw_id, request.agent_name)
|
|
40
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
41
|
+
|
|
42
|
+
prompt_request = AgentPromptRequest(
|
|
43
|
+
prompt=prompt,
|
|
44
|
+
adw_id=request.adw_id,
|
|
45
|
+
agent_name=request.agent_name,
|
|
46
|
+
model=request.model,
|
|
47
|
+
dangerously_skip_permissions=True,
|
|
48
|
+
output_file=os.path.join(output_dir, "raw_output.jsonl"),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return execute_with_provider(prompt_request)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ── Backward-compatible wrappers ──
|
|
55
|
+
# These functions preserve the original agent.py interface so that
|
|
56
|
+
# existing callers (adw_plan_build.py, etc.) continue to work.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def check_agent_available() -> Optional[str]:
|
|
60
|
+
"""Check if the configured agent provider is available."""
|
|
61
|
+
provider = get_current_provider()
|
|
62
|
+
return provider.check_available()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_agent_env() -> dict:
|
|
66
|
+
"""Get environment variables for the configured agent provider."""
|
|
67
|
+
provider = get_current_provider()
|
|
68
|
+
return provider.get_env()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ── Legacy Claude Code functions (kept for backward compatibility) ──
|
|
72
|
+
# Callers that specifically need Claude Code CLI can still use these.
|
|
73
|
+
|
|
74
|
+
def check_claude_installed() -> Optional[str]:
|
|
75
|
+
"""Check if Claude Code CLI is installed. Return error message if not."""
|
|
76
|
+
provider = ClaudeCLIProvider()
|
|
77
|
+
return provider.check_available()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def prompt_claude_code(request: AgentPromptRequest) -> AgentPromptResponse:
|
|
81
|
+
"""Execute Claude Code with the given prompt configuration (legacy)."""
|
|
82
|
+
provider = ClaudeCLIProvider()
|
|
83
|
+
return provider.execute_prompt(request)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def execute_template(request: AgentTemplateRequest) -> AgentPromptResponse:
|
|
87
|
+
"""Execute a Claude Code template with slash command and args (legacy)."""
|
|
88
|
+
return execute_template_with_provider(request)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_claude_env() -> dict:
|
|
92
|
+
"""Get environment variables for Claude Code execution (legacy)."""
|
|
93
|
+
provider = ClaudeCLIProvider()
|
|
94
|
+
return provider.get_env()
|