@@ -281,6 +281,139 @@ def get_org_gh_from_profile(repo_id: str) -> Optional[str]:
281281 return None
282282
283283
284+ def build_docker_image (repo_id : str ) -> bool :
285+ """Build Docker image for the repository.
286+
287+ Note: generate_profile.py cleans up images after Stage 2, so we need to rebuild.
288+
289+ Args:
290+ repo_id: Repository ID (e.g., google__gson.dd2fe59c)
291+
292+ Returns:
293+ True if build successful, False if failed
294+ """
295+ try :
296+ from swesmith .profiles import registry
297+ profile = registry .get (repo_id )
298+ image_name = profile .image_name
299+
300+ print (f"\n 🏗️ Building Docker image for Modal..." )
301+ print (f" Image: { image_name } " )
302+ print (f" (Note: generate_profile.py cleans up after Stage 2, so we rebuild for Modal)" )
303+
304+ # Check if image already exists
305+ check_result = subprocess .run (
306+ ["docker" , "image" , "inspect" , image_name ],
307+ capture_output = True ,
308+ check = False
309+ )
310+
311+ if check_result .returncode == 0 :
312+ print (f"✅ Docker image already exists locally" )
313+ return True
314+
315+ # Build from the profile's Dockerfile property
316+ from swesmith .profiles import registry
317+ profile = registry .get (repo_id )
318+
319+ # Create temporary build directory
320+ import tempfile
321+ with tempfile .TemporaryDirectory () as tmpdir :
322+ dockerfile_path = Path (tmpdir ) / "Dockerfile"
323+
324+ # Write the profile's Dockerfile
325+ print (f" Using Dockerfile from profile registry" )
326+ with open (dockerfile_path , 'w' ) as f :
327+ f .write (profile .dockerfile )
328+
329+ # Build the image
330+ build_cmd = [
331+ "docker" ,
332+ "build" ,
333+ "--no-cache" , # Force rebuild to avoid stale cached layers
334+ "-f" , str (dockerfile_path ),
335+ "-t" , image_name ,
336+ tmpdir # Build context
337+ ]
338+
339+ print (f" Building: { ' ' .join (build_cmd )} " )
340+ build_result = subprocess .run (
341+ build_cmd ,
342+ capture_output = False , # Show build progress
343+ check = False
344+ )
345+
346+ # Check if build was successful by verifying the image exists
347+ verify_result = subprocess .run (
348+ ["docker" , "image" , "inspect" , image_name ],
349+ capture_output = True ,
350+ check = False
351+ )
352+
353+ if verify_result .returncode == 0 :
354+ print (f"✅ Successfully built Docker image" )
355+ return True
356+ else :
357+ print (f"❌ Failed to build Docker image" )
358+ print (f" The build command may have failed internally" )
359+ print (f" Check the output above for errors (e.g., missing GITHUB_TOKEN)" )
360+ return False
361+
362+ except Exception as e :
363+ print (f"❌ Error building Docker image: { e } " )
364+ return False
365+
366+
367+ def push_docker_image (repo_id : str ) -> bool :
368+ """Push Docker image to Docker Hub after building.
369+
370+ Args:
371+ repo_id: Repository ID (e.g., google__gson.dd2fe59c)
372+
373+ Returns:
374+ True if push successful or not needed, False if failed
375+ """
376+ try :
377+ from swesmith .profiles import registry
378+ profile = registry .get (repo_id )
379+ image_name = profile .image_name
380+
381+ print (f"\n 🐳 Pushing Docker image to Docker Hub..." )
382+ print (f" Image: { image_name } " )
383+
384+ # Check if image exists locally first
385+ check_result = subprocess .run (
386+ ["docker" , "image" , "inspect" , image_name ],
387+ capture_output = True ,
388+ check = False
389+ )
390+
391+ if check_result .returncode != 0 :
392+ print (f"⚠️ Warning: Docker image { image_name } not found locally" )
393+ print (" Skipping push - image will need to be built first" )
394+ return False
395+
396+ # Push the image
397+ push_result = subprocess .run (
398+ ["docker" , "push" , image_name ],
399+ capture_output = False , # Show progress
400+ check = False
401+ )
402+
403+ if push_result .returncode == 0 :
404+ print (f"✅ Successfully pushed Docker image to Docker Hub" )
405+ return True
406+ else :
407+ print (f"❌ Failed to push Docker image (exit code: { push_result .returncode } )" )
408+ print (" Make sure you're logged in to Docker Hub: docker login" )
409+ print (" Make sure you have permission to push to this repository" )
410+ return False
411+
412+ except Exception as e :
413+ print (f"❌ Error pushing Docker image: { e } " )
414+ return False
415+
416+
284417def main ():
285418 parser = argparse .ArgumentParser (
286419 description = "End-to-end bug generation pipeline: profile → bugs → analysis" ,
@@ -369,8 +502,8 @@ def main():
369502 )
370503 parser .add_argument (
371504 '--org-dh' ,
372- default = 'cs329a-swesmith ' ,
373- help = 'Docker Hub organization for images (default: cs329a-swesmith )'
505+ default = 'priyank0003 ' ,
506+ help = 'Docker Hub organization for images (default: priyank0003 )'
374507 )
375508
376509 # Pipeline control
@@ -379,6 +512,11 @@ def main():
379512 action = 'store_true' ,
380513 help = 'Skip profile generation (profile already exists in registry)'
381514 )
515+ parser .add_argument (
516+ '--skip-agent' ,
517+ action = 'store_true' ,
518+ help = 'Skip agent run but regenerate profile from existing agent-result directory'
519+ )
382520 parser .add_argument (
383521 '--skip-bug-gen' ,
384522 action = 'store_true' ,
@@ -389,6 +527,11 @@ def main():
389527 action = 'store_true' ,
390528 help = 'Skip bug analysis'
391529 )
530+ parser .add_argument (
531+ '--force-docker-push' ,
532+ action = 'store_true' ,
533+ help = 'Force Docker image build and push (useful for testing Docker workflow without re-running profile gen)'
534+ )
392535
393536 args = parser .parse_args ()
394537
@@ -408,30 +551,44 @@ def main():
408551
409552 output_log = Path (f"{ owner } -{ repo } -gen.out" )
410553
411- # Build command for generate_profile.py
412- gen_cmd = [
413- sys .executable ,
414- "mini-swe-agent-automate-repo-installation/generate_profile.py" ,
415- args .repo_name ,
416- "--model" , args .model ,
417- "--max-cost" , str (args .max_cost ),
418- "--max-time" , str (args .max_time ),
419- ]
420-
421- if args .language .lower () == 'python' :
422- gen_cmd .append ('--python-repo' )
423-
424- if args .livestream :
425- gen_cmd .append ('--livestream' )
426-
427- if args .verify :
428- gen_cmd .append ('--verify' )
429-
430- exit_code , _ = run_command (gen_cmd , "Generating profile" , tee_output = output_log )
431-
432- if exit_code != 0 :
433- print (f"\n ❌ Profile generation failed. Check { output_log } for details." )
434- sys .exit (1 )
554+ # Check if we should skip agent run but regenerate profile
555+ if args .skip_agent :
556+ print ("\n ⏭️ Skipping agent run (--skip-agent)" )
557+ print (" Reusing existing agent-result directory" )
558+
559+ # Check if agent-result directory exists
560+ agent_result_dir = Path (f"agent-result/{ owner } -{ repo } " )
561+ if not agent_result_dir .exists ():
562+ print (f"\n ❌ Agent result directory not found: { agent_result_dir } " )
563+ print (" Cannot skip agent run without existing agent results" )
564+ sys .exit (1 )
565+
566+ print (f"✅ Found existing agent-result directory: { agent_result_dir } " )
567+ else :
568+ # Build command for generate_profile.py
569+ gen_cmd = [
570+ sys .executable ,
571+ "mini-swe-agent-automate-repo-installation/generate_profile.py" ,
572+ args .repo_name ,
573+ "--model" , args .model ,
574+ "--max-cost" , str (args .max_cost ),
575+ "--max-time" , str (args .max_time ),
576+ ]
577+
578+ if args .language .lower () == 'python' :
579+ gen_cmd .append ('--python-repo' )
580+
581+ if args .livestream :
582+ gen_cmd .append ('--livestream' )
583+
584+ if args .verify :
585+ gen_cmd .append ('--verify' )
586+
587+ exit_code , _ = run_command (gen_cmd , "Generating profile" , tee_output = output_log )
588+
589+ if exit_code != 0 :
590+ print (f"\n ❌ Profile generation failed. Check { output_log } for details." )
591+ sys .exit (1 )
435592
436593 # Load and insert the generated profile
437594 result = load_generated_profile (args .repo_name )
@@ -488,6 +645,38 @@ def main():
488645
489646 print (f"\n ✅ Found repo_id in registry: { repo_id } " )
490647
648+ # Build and push Docker image to Docker Hub if using Modal validation
649+ # (Modal needs to pull the image from a registry)
650+ # Note: generate_profile.py cleans up the image after Stage 2, so we rebuild
651+ if (args .use_modal and not args .skip_profile_gen ) or args .force_docker_push :
652+ if args .force_docker_push and args .skip_profile_gen :
653+ print ("\n " + "=" * 80 )
654+ print ("DOCKER BUILD & PUSH (--force-docker-push)" )
655+ print ("=" * 80 )
656+
657+ # First, rebuild the image (it was cleaned up by generate_profile.py)
658+ build_success = build_docker_image (repo_id )
659+ if not build_success :
660+ print ("\n ❌ Failed to build Docker image" )
661+ print (" Modal validation requires a Docker image" )
662+ sys .exit (1 )
663+
664+ # Then, push it to Docker Hub
665+ push_success = push_docker_image (repo_id )
666+ if not push_success :
667+ print ("\n ⚠️ Warning: Failed to push Docker image to Docker Hub" )
668+ print (" Modal validation requires the image to be available on Docker Hub" )
669+ print (" You can:" )
670+ print (" 1. Login to Docker Hub: docker login" )
671+ print (" 2. Manually push the image later" )
672+ print (" 3. Continue with local validation (remove --use-modal flag)" )
673+
674+ # Ask user if they want to continue
675+ response = input ("\n ❓ Continue anyway? (y/N): " ).strip ().lower ()
676+ if response not in ['y' , 'yes' ]:
677+ print ("Exiting..." )
678+ sys .exit (1 )
679+
491680 # Step 2: Generate bugs (unless skipped)
492681 if not args .skip_bug_gen :
493682 print ("\n " + "=" * 80 )
@@ -586,16 +775,12 @@ def main():
586775 print ("STEP 3: BUG ANALYSIS" )
587776 print ("=" * 80 )
588777
589- # Get org_gh to construct the correct path
590- org_gh = get_org_gh_from_profile (repo_id )
591- analysis_repo_id = f"{ org_gh } /{ repo_id } " if org_gh else repo_id
592-
593- print (f"📂 Analysis path: logs/bug_gen/{ analysis_repo_id } " )
778+ print (f"📂 Analysis path: logs/bug_gen/{ repo_id } " )
594779
595780 analysis_cmd = [
596781 sys .executable ,
597782 "scripts/analyze_bugs.py" ,
598- analysis_repo_id
783+ repo_id
599784 ]
600785
601786 exit_code , _ = run_command (analysis_cmd , "Analyzing generated bugs" , capture_output = True )
@@ -617,16 +802,12 @@ def main():
617802 if not args .skip_bug_gen :
618803 print (f"Validation: { 'Modal (parallel)' if args .use_modal else 'Local' } " )
619804
620- # Construct paths with org_gh if available
621- org_gh = get_org_gh_from_profile (repo_id )
622- path_prefix = f"{ org_gh } /{ repo_id } " if org_gh else repo_id
623-
624805 print (f"\n Generated artifacts:" )
625806 print (f" - Profile: { get_profile_file_for_language (args .language )} " )
626- print (f" - Bugs: logs/bug_gen/{ path_prefix } /" )
627- print (f" - Patches: logs/bug_gen/{ path_prefix } _all_patches.json" )
628- print (f" - Validation: logs/run_validation/{ path_prefix } /" )
629- print (f" - Analysis: logs/analysis/{ path_prefix } _analysis.json" )
807+ print (f" - Bugs: logs/bug_gen/{ repo_id } /" )
808+ print (f" - Patches: logs/bug_gen/{ repo_id } _all_patches.json" )
809+ print (f" - Validation: logs/run_validation/{ repo_id } /" )
810+ print (f" - Analysis: logs/analysis/{ repo_id } _analysis.json" )
630811 print ("\n " + "=" * 80 )
631812
632813 except KeyboardInterrupt :
0 commit comments