stage-1: project bootstrap
Structure, config loader, personality/tools/memory from clawd, venv, 22 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
209
tools/ralph_workflow.py
Executable file
209
tools/ralph_workflow.py
Executable file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Ralph Workflow Helper - pentru Echo
|
||||
Gestionează workflow-ul complet de creare și execuție proiecte cu Ralph + Claude Code
|
||||
Folosește ralph_prd_generator.py pentru PRD, apoi ralph.sh pentru implementare
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# Import generator PRD
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
from ralph_prd_generator import create_prd_and_json
|
||||
|
||||
WORKSPACE = Path.home() / "workspace"
|
||||
|
||||
|
||||
def run_ralph(prd_json: Path, max_iterations: int = 20, background: bool = False):
|
||||
"""
|
||||
Rulează ralph.sh pentru execuție autonomă
|
||||
|
||||
Args:
|
||||
prd_json: Path către prd.json
|
||||
max_iterations: Max iterații Ralph
|
||||
background: Dacă True, rulează în background
|
||||
|
||||
Returns:
|
||||
subprocess.Popen object dacă background, altfel exit code
|
||||
"""
|
||||
project_dir = prd_json.parent.parent.parent
|
||||
ralph_script = prd_json.parent / "ralph.sh"
|
||||
|
||||
if not ralph_script.exists():
|
||||
print(f"❌ ralph.sh nu există în {ralph_script.parent}")
|
||||
return None
|
||||
|
||||
print(f"\n🚀 Lansez Ralph loop")
|
||||
print(f"📁 Project: {project_dir}")
|
||||
print(f"🔁 Max iterations: {max_iterations}")
|
||||
print(f"🌙 Background: {background}")
|
||||
|
||||
cmd = [str(ralph_script), str(max_iterations)]
|
||||
|
||||
if background:
|
||||
log_file = ralph_script.parent / "logs" / "ralph.log"
|
||||
log_file.parent.mkdir(exist_ok=True)
|
||||
|
||||
with open(log_file, 'w') as f:
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=project_dir,
|
||||
stdout=f,
|
||||
stderr=subprocess.STDOUT,
|
||||
start_new_session=True
|
||||
)
|
||||
|
||||
print(f"✅ Ralph pornit în background (PID: {process.pid})")
|
||||
print(f"📋 Log: {log_file}")
|
||||
|
||||
pid_file = ralph_script.parent / ".ralph.pid"
|
||||
with open(pid_file, 'w') as f:
|
||||
f.write(str(process.pid))
|
||||
|
||||
return process
|
||||
else:
|
||||
print("⚠️ Rulare în foreground")
|
||||
result = subprocess.run(cmd, cwd=project_dir)
|
||||
return result.returncode
|
||||
|
||||
|
||||
def check_status(project_dir: Path):
|
||||
"""Verifică status Ralph pentru un proiect"""
|
||||
prd_json = project_dir / "scripts" / "ralph" / "prd.json"
|
||||
progress_file = project_dir / "scripts" / "ralph" / "progress.txt"
|
||||
pid_file = project_dir / "scripts" / "ralph" / ".ralph.pid"
|
||||
|
||||
status = {
|
||||
"project": project_dir.name,
|
||||
"running": False,
|
||||
"complete": [],
|
||||
"incomplete": [],
|
||||
"learnings": []
|
||||
}
|
||||
|
||||
if pid_file.exists():
|
||||
with open(pid_file) as f:
|
||||
pid = int(f.read().strip())
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
status["running"] = True
|
||||
status["pid"] = pid
|
||||
except OSError:
|
||||
status["running"] = False
|
||||
|
||||
if prd_json.exists():
|
||||
with open(prd_json) as f:
|
||||
data = json.load(f)
|
||||
for story in data.get('userStories', []):
|
||||
if story.get('passes'):
|
||||
status["complete"].append({
|
||||
"id": story['id'],
|
||||
"title": story['title']
|
||||
})
|
||||
else:
|
||||
status["incomplete"].append({
|
||||
"id": story['id'],
|
||||
"title": story['title'],
|
||||
"priority": story.get('priority', 999)
|
||||
})
|
||||
|
||||
if progress_file.exists():
|
||||
with open(progress_file) as f:
|
||||
lines = f.readlines()
|
||||
status["learnings"] = [l.strip() for l in lines[-10:] if l.strip()]
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI pentru testing"""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage:")
|
||||
print(" python ralph_workflow.py create PROJECT_NAME 'description'")
|
||||
print(" python ralph_workflow.py status PROJECT_NAME")
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1]
|
||||
|
||||
if command == "create":
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: python ralph_workflow.py create PROJECT_NAME 'description'")
|
||||
sys.exit(1)
|
||||
|
||||
project_name = sys.argv[2]
|
||||
description = sys.argv[3]
|
||||
|
||||
print("=" * 70)
|
||||
print(f"🧪 Ralph workflow: {project_name}")
|
||||
print("=" * 70)
|
||||
|
||||
# Generează PRD și prd.json
|
||||
prd_file, prd_json = create_prd_and_json(project_name, description, WORKSPACE)
|
||||
|
||||
if not prd_file or not prd_json:
|
||||
print("\n❌ Eroare la generare PRD")
|
||||
sys.exit(1)
|
||||
|
||||
# Lansează Ralph în background
|
||||
process = run_ralph(prd_json, max_iterations=20, background=True)
|
||||
|
||||
if process:
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ Workflow complet!")
|
||||
print(f"📁 Project: {prd_json.parent.parent.parent}")
|
||||
print(f"🔄 Ralph PID: {process.pid}")
|
||||
print(f"📋 Monitor: tail -f {prd_json.parent}/logs/ralph.log")
|
||||
print("=" * 70)
|
||||
else:
|
||||
print("\n❌ Eroare la lansare Ralph")
|
||||
sys.exit(1)
|
||||
|
||||
elif command == "status":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python ralph_workflow.py status PROJECT_NAME")
|
||||
sys.exit(1)
|
||||
|
||||
project_name = sys.argv[2]
|
||||
project_dir = WORKSPACE / project_name
|
||||
|
||||
if not project_dir.exists():
|
||||
print(f"❌ Proiect nu există: {project_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
status = check_status(project_dir)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(f"📊 Status: {status['project']}")
|
||||
print("=" * 70)
|
||||
print(f"🔄 Running: {'DA (PID: ' + str(status.get('pid', '')) + ')' if status['running'] else 'NU'}")
|
||||
print(f"✅ Complete: {len(status['complete'])}")
|
||||
print(f"🔄 Incomplete: {len(status['incomplete'])}")
|
||||
|
||||
if status['complete']:
|
||||
print("\n✅ Stories complete:")
|
||||
for s in status['complete'][:5]:
|
||||
print(f" - {s['id']}: {s['title']}")
|
||||
|
||||
if status['incomplete']:
|
||||
print("\n🔄 Stories incomplete:")
|
||||
for s in sorted(status['incomplete'], key=lambda x: x['priority'])[:5]:
|
||||
print(f" - {s['id']} (P{s['priority']}): {s['title']}")
|
||||
|
||||
if status['learnings']:
|
||||
print("\n📚 Recent learnings:")
|
||||
for l in status['learnings'][-5:]:
|
||||
print(f" {l}")
|
||||
|
||||
print("=" * 70)
|
||||
|
||||
else:
|
||||
print(f"❌ Comandă necunoscută: {command}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user