-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup-run.py
More file actions
executable file
·556 lines (453 loc) · 18.3 KB
/
setup-run.py
File metadata and controls
executable file
·556 lines (453 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
#!/usr/bin/env python3
"""
Agent Comparison Setup Tool
A stylish CLI tool to set up new agent comparison runs.
No dependencies required - uses only Python standard library.
"""
import os
import shutil
import subprocess
import sys
from pathlib import Path
class Colors:
"""ANSI color codes for terminal styling."""
HEADER = '\033[95m'
BLUE = '\033[94m'
CYAN = '\033[96m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
DIM = '\033[2m'
def print_header(text):
"""Print a styled header."""
print(f"\n{Colors.BOLD}{Colors.CYAN}{'=' * 60}{Colors.ENDC}")
print(f"{Colors.BOLD}{Colors.CYAN}{text.center(60)}{Colors.ENDC}")
print(f"{Colors.BOLD}{Colors.CYAN}{'=' * 60}{Colors.ENDC}\n")
def print_section(text):
"""Print a section title."""
print(f"\n{Colors.BOLD}{Colors.BLUE}{text}{Colors.ENDC}")
def print_success(text):
"""Print a success message."""
print(f"{Colors.GREEN}✓ {text}{Colors.ENDC}")
def print_error(text):
"""Print an error message."""
print(f"{Colors.RED}✗ {text}{Colors.ENDC}")
def print_info(text):
"""Print an info message."""
print(f"{Colors.YELLOW}ℹ {text}{Colors.ENDC}")
def get_repo_root():
"""Get the repository root directory."""
return Path(__file__).parent.resolve()
def discover_use_cases():
"""Discover all use cases by scanning directories."""
repo_root = get_repo_root()
use_cases = []
for item in sorted(repo_root.iterdir()):
# Match any directory starting with XX- where XX is 01-99
if item.is_dir() and len(item.name) > 3 and item.name[:2].isdigit() and item.name[2] == '-':
base_dir = item / '_base'
if base_dir.exists():
# Try to read README for description
readme_path = item / 'README.md'
description = "No description available"
if readme_path.exists():
with open(readme_path, 'r') as f:
lines = f.readlines()
# Look for the first line after the title (usually a description)
for i, line in enumerate(lines):
if line.startswith('**') and '**' in line[2:]:
description = line.strip('*').strip()
break
use_cases.append({
'id': item.name,
'name': item.name,
'path': item,
'description': description
})
return use_cases
def print_menu(items, title="Select an option"):
"""Print a numbered menu and return user selection."""
print_section(title)
for i, item in enumerate(items, 1):
if isinstance(item, dict):
desc = item.get('description', '')
name = item.get('name', str(item))
print(f" {Colors.CYAN}{i}.{Colors.ENDC} {Colors.BOLD}{name}{Colors.ENDC}")
if desc:
print(f" {Colors.DIM}{desc}{Colors.ENDC}")
else:
print(f" {Colors.CYAN}{i}.{Colors.ENDC} {item}")
while True:
try:
choice = input(f"\n{Colors.BOLD}Enter your choice (1-{len(items)}): {Colors.ENDC}")
choice_num = int(choice)
if 1 <= choice_num <= len(items):
return choice_num - 1
else:
print_error(f"Please enter a number between 1 and {len(items)}")
except ValueError:
print_error("Please enter a valid number")
except KeyboardInterrupt:
print("\n")
print_error("Cancelled by user")
sys.exit(0)
def get_text_input(prompt, allow_empty=False):
"""Get text input from user."""
while True:
try:
value = input(f"{Colors.BOLD}{prompt}{Colors.ENDC}").strip()
if value or allow_empty:
return value
else:
print_error("Input cannot be empty")
except KeyboardInterrupt:
print("\n")
print_error("Cancelled by user")
sys.exit(0)
def sanitize_branch_name(name):
"""Sanitize a string to be valid as a git branch name."""
# Replace invalid characters with hyphens
valid = name.replace('/', '-').replace(' ', '-').replace('_', '-')
# Remove any characters that aren't alphanumeric or hyphens
valid = ''.join(c for c in valid if c.isalnum() or c == '-')
# Remove duplicate hyphens
while '--' in valid:
valid = valid.replace('--', '-')
# Remove leading/trailing hyphens
return valid.strip('-').lower()
def check_git_status():
"""Check if we're in a git repository and it's clean."""
try:
# Check if we're in a git repo
result = subprocess.run(
['git', 'rev-parse', '--git-dir'],
cwd=get_repo_root(),
capture_output=True,
text=True
)
if result.returncode != 0:
return False, "Not in a git repository"
# Check for uncommitted changes
result = subprocess.run(
['git', 'status', '--porcelain'],
cwd=get_repo_root(),
capture_output=True,
text=True
)
if result.stdout.strip():
print_info("You have uncommitted changes:")
print(result.stdout)
response = get_text_input("Continue anyway? (y/n): ")
if response.lower() != 'y':
return False, "Uncommitted changes detected"
return True, None
except FileNotFoundError:
return False, "Git is not installed"
def create_branch(branch_name):
"""Create and checkout a new git branch."""
try:
# Create and checkout new branch
result = subprocess.run(
['git', 'checkout', '-b', branch_name],
cwd=get_repo_root(),
capture_output=True,
text=True
)
if result.returncode != 0:
if 'already exists' in result.stderr:
print_error(f"Branch '{branch_name}' already exists")
response = get_text_input("Switch to existing branch? (y/n): ")
if response.lower() == 'y':
subprocess.run(['git', 'checkout', branch_name], cwd=get_repo_root())
return True
return False
else:
print_error(f"Failed to create branch: {result.stderr}")
return False
print_success(f"Created and switched to branch: {branch_name}")
return True
except Exception as e:
print_error(f"Error creating branch: {e}")
return False
def find_available_path(target_path):
"""Find an available path by enumerating if necessary."""
if not target_path.exists():
return target_path
# Find the highest existing enumeration
base_name = target_path.name
parent = target_path.parent
counter = 2
# Check for existing enumerated folders
while True:
enumerated_name = f"{base_name}_{counter}"
enumerated_path = parent / enumerated_name
if not enumerated_path.exists():
return enumerated_path
counter += 1
def copy_base_to_target(base_path, target_path):
"""Copy _base folder contents to target location."""
try:
# Create parent directories if needed
target_path.parent.mkdir(parents=True, exist_ok=True)
# Find available path (enumerate if needed)
original_path = target_path
target_path = find_available_path(target_path)
if target_path != original_path:
print_info(f"Target exists, using enumerated path: {target_path.name}")
# Copy _base contents
shutil.copytree(base_path, target_path)
print_success(f"Copied base to: {target_path.relative_to(get_repo_root())}")
return target_path
except Exception as e:
print_error(f"Error copying files: {e}")
return None
def setup_evaluation_template(target_path, use_case_path, is_orchestration):
"""Copy evaluation template and create .report/ folder."""
repo_root = get_repo_root()
# Determine which evaluation template to use
# First try use-case specific template, fall back to generic
if is_orchestration:
use_case_template = use_case_path / 'EVALUATION_ORCHESTRATION.md'
generic_template = repo_root / 'EVALUATION_REPORT_ORCHESTRATION.md'
else:
use_case_template = use_case_path / 'EVALUATION_CODING_AGENT.md'
generic_template = repo_root / 'EVALUATION_REPORT_CODING_AGENT.md'
# Choose template source
if use_case_template.exists():
template_source = use_case_template
print_info(f"Using use-case specific template")
elif generic_template.exists():
template_source = generic_template
print_info(f"Using generic template")
else:
print_error("No evaluation template found!")
return False
try:
# Create .report/ folder
report_dir = target_path / '.report'
report_dir.mkdir(exist_ok=True)
# Create screenshots subfolder
(report_dir / 'screenshots').mkdir(exist_ok=True)
# Copy evaluation template to .report/ folder
eval_dest = report_dir / 'EVALUATION.md'
shutil.copy(template_source, eval_dest)
# Create a README in .report explaining its purpose
report_readme = report_dir / 'README.md'
with open(report_readme, 'w') as f:
f.write("""# Evaluation Assets
This folder contains all assets related to evaluating this agent run:
- **EVALUATION.md** - Structured evaluation checklist for this run
- **screenshots/** - Screenshots of the application, errors, interesting moments
- **chat-log.md** - Conversation history with the agent (optional)
- **transcript.json** - Full session transcript (optional)
- **session-notes.md** - Manual notes about the session (optional)
Fill out EVALUATION.md after completing your run to document what worked and what didn't.
""")
print_success(f"Created .report/ folder with evaluation template")
return True
except Exception as e:
print_error(f"Error setting up evaluation: {e}")
return False
def scaffold_index_entry(use_case_path, target_path, is_orchestration, agent_harness, model_name, paradigm_name=None):
"""Add a scaffolded entry to the appropriate index file."""
try:
# Determine index file path
if is_orchestration:
index_file = use_case_path / 'INDEX_ORCHESTRATION.md'
else:
index_file = use_case_path / 'INDEX_CODING_AGENTS.md'
if not index_file.exists():
print_info(f"Index file not found, skipping scaffolding")
return False
# Read existing index
with open(index_file, 'r') as f:
content = f.read()
# Build relative path from use case to target
try:
rel_path = target_path.relative_to(use_case_path)
except ValueError:
rel_path = target_path
# Get current date
from datetime import datetime
today = datetime.now().strftime("%b %d, %Y")
# Create scaffolded entry
if is_orchestration:
entry = f"""
## {paradigm_name.upper()} - {agent_harness.title()} - {model_name.title()} - {today}

**Status:** ✅ Success | ⚠️ Partial | ❌ Failed
**Time:** `[X hours]`
**Score:** `[X/30]` ([detailed report]({rel_path}/.report/EVALUATION.md))
**Quick Summary:**
```
[Fill in after run - 2-3 sentences about how orchestration worked]
```
**Workflow:**
- [ ] Specification phase completed
- [ ] Architecture phase completed
- [ ] Implementation phase completed
- [ ] Testing phase completed
**Core Features:**
- [ ] [Feature 1]
- [ ] [Feature 2]
- [ ] [Feature 3]
- [ ] [Feature 4]
**Rating:** ⭐⭐⭐⭐⭐ `X/5` - `[Recommendation]`
---
"""
else:
entry = f"""
## {agent_harness.title()} - {model_name.title()} - {today}

**Status:** ✅ Success | ⚠️ Partial | ❌ Failed
**Time:** `[X hours]`
**Score:** `[X/30]` ([detailed report]({rel_path}/.report/EVALUATION.md))
**Quick Summary:**
```
[Fill in after run - 2-3 sentences about what worked/didn't work]
```
**Core Features:**
- [ ] [Feature 1]
- [ ] [Feature 2]
- [ ] [Feature 3]
- [ ] [Feature 4]
**Rating:** ⭐⭐⭐⭐⭐ `X/5` - `[Recommendation]`
---
"""
# Find insertion point (before "## Summary Statistics")
summary_marker = "## Summary Statistics"
if summary_marker in content:
# Insert before summary
parts = content.split(summary_marker, 1)
new_content = parts[0] + entry + "\n" + summary_marker + parts[1]
else:
# Append to end
new_content = content + "\n" + entry
# Write updated index
with open(index_file, 'w') as f:
f.write(new_content)
print_success(f"Scaffolded entry in {index_file.name}")
return True
except Exception as e:
print_error(f"Error scaffolding index entry: {e}")
return False
def main():
"""Main application flow."""
print_header("Agent Comparison Setup Tool")
# Check git status
git_ok, git_error = check_git_status()
if not git_ok:
print_error(f"Git check failed: {git_error}")
print_info("Continuing without git branch creation...")
create_git_branch = False
else:
print_success("Git repository is ready")
create_git_branch = True
# Discover use cases
use_cases = discover_use_cases()
if not use_cases:
print_error("No use cases found!")
return 1
# Step 1: Select use case
use_case_idx = print_menu(use_cases, "📋 Select Use Case")
use_case = use_cases[use_case_idx]
print_success(f"Selected: {use_case['name']}")
# Step 2: Select run type (coding agent or orchestration)
run_types = [
"Coding Agent (one-shot)",
"Orchestration Paradigm"
]
run_type_idx = print_menu(run_types, "🎯 Select Run Type")
is_orchestration = run_type_idx == 1
# Step 3: Get paradigm name if orchestration
paradigm_name = None
if is_orchestration:
print_section("🔧 Orchestration Paradigm")
print_info("Examples: bmad, spec-kit, openspec, custom-flow")
paradigm_name = get_text_input("Enter paradigm name: ")
paradigm_name = sanitize_branch_name(paradigm_name)
print_success(f"Paradigm: {paradigm_name}")
# Step 4: Get agent harness
print_section("🤖 Agent Harness")
print_info("Examples: codexcli, geminicli, cursor, windsurf, aider")
agent_harness = get_text_input("Enter agent harness name: ")
agent_harness = sanitize_branch_name(agent_harness)
print_success(f"Harness: {agent_harness}")
# Step 5: Get model name
print_section("🧠 Model")
print_info("Examples: gpt-5-high, claude-sonnet, gemini-2-flash, codex-max")
model_name = get_text_input("Enter model name: ")
model_name = sanitize_branch_name(model_name)
print_success(f"Model: {model_name}")
# Build paths and branch name
base_path = use_case['path'] / '_base'
if is_orchestration:
target_path = use_case['path'] / 'orchestration' / paradigm_name / f"{agent_harness}_{model_name}"
branch_name = f"{paradigm_name}_{agent_harness}_{model_name}"
else:
target_path = use_case['path'] / 'coding_agents' / f"{agent_harness}_{model_name}"
branch_name = f"{agent_harness}_{model_name}"
# Summary
print_header("Summary")
print(f"{Colors.BOLD}Use Case:{Colors.ENDC} {use_case['name']}")
print(f"{Colors.BOLD}Type:{Colors.ENDC} {'Orchestration' if is_orchestration else 'Coding Agent'}")
if is_orchestration:
print(f"{Colors.BOLD}Paradigm:{Colors.ENDC} {paradigm_name}")
print(f"{Colors.BOLD}Agent Harness:{Colors.ENDC} {agent_harness}")
print(f"{Colors.BOLD}Model:{Colors.ENDC} {model_name}")
print(f"{Colors.BOLD}Target Path:{Colors.ENDC} {target_path.relative_to(get_repo_root())}")
print(f"{Colors.BOLD}Branch Name:{Colors.ENDC} {branch_name}")
# Confirm
print()
response = get_text_input("Proceed with setup? (y/n): ")
if response.lower() != 'y':
print_error("Setup cancelled")
return 1
# Execute setup
print_header("Setting Up")
# Copy files
actual_target = copy_base_to_target(base_path, target_path)
if not actual_target:
return 1
# Update target_path to actual location used
target_path = actual_target
# Setup evaluation template and .report/ folder
setup_evaluation_template(target_path, use_case['path'], is_orchestration)
# Scaffold index entry
scaffold_index_entry(
use_case['path'],
target_path,
is_orchestration,
agent_harness,
model_name,
paradigm_name
)
# Create git branch
if create_git_branch:
if not create_branch(branch_name):
print_info("Continuing without branch creation...")
# Final message
print_header("Setup Complete!")
print(f"{Colors.GREEN}✓ Your workspace is ready at:{Colors.ENDC}")
print(f" {Colors.BOLD}{target_path.relative_to(get_repo_root())}{Colors.ENDC}")
print()
print(f"{Colors.CYAN}Next steps:{Colors.ENDC}")
print(f" 1. cd {target_path.relative_to(get_repo_root())}")
print(f" 2. Read {Colors.BOLD}prompt.md{Colors.ENDC}")
print(f" 3. Start your agent with the prompt")
print(f" 4. After completion:")
print(f" - Add screenshot as {Colors.BOLD}.report/screenshot.png{Colors.ENDC}")
print(f" - Fill out {Colors.BOLD}.report/EVALUATION.md{Colors.ENDC}")
print(f" - Update the index entry in {Colors.BOLD}{'INDEX_ORCHESTRATION.md' if is_orchestration else 'INDEX_CODING_AGENTS.md'}{Colors.ENDC}")
print(f" - Optional: Add session logs to {Colors.BOLD}.report/session-logs/{Colors.ENDC}")
print()
return 0
if __name__ == '__main__':
try:
sys.exit(main())
except KeyboardInterrupt:
print("\n")
print_error("Interrupted by user")
sys.exit(1)