@@ -174,12 +174,17 @@ The user wants to create a new application from scratch with the following descr
174174"%s"
175175
176176Please provide an implementation plan. Include:
177- 1. A project name. If the user specified a name, use that exact name. Otherwise suggest a short, lowercase, hyphenated name.
177+ 1. A project name. If the user specified a name, use that EXACT name as-is . Otherwise suggest a short, lowercase, hyphenated name.
1781782. A high-level architecture overview.
179- 3. The specific files and folder structure that will be created.
179+ 3. The COMPLETE files and folder structure that will be created. List EVERY file with its full relative path from the project root (e.g. "backend/main.go", "frontend/index.html", "frontend/styles.css"). If the application has multiple components (frontend, backend, API, etc.), organize them into separate folders .
1801804. The commands needed to initialize dependencies (e.g. go mod init, pip install).
1811815. The command to run the application to test it.
182- IMPORTANT: Use the programming language the user requested. If no language is specified, choose the most appropriate one.` , c .Description )
182+
183+ IMPORTANT RULES:
184+ - Use the programming language the user requested. If no language is specified, choose the most appropriate one.
185+ - If the user asks for a web application, you MUST include both frontend AND backend folders with complete implementations.
186+ - List every single file that will be created — do not summarize with "..." or "etc".
187+ - The project name MUST appear as "Project Name: <name>" on its own line.` , c .Description )
183188
184189 plan , err := aicall (c .AIClient , c .Model , prompt )
185190 if err != nil {
@@ -395,23 +400,34 @@ Assume we are already inside the project directory.`, c.Plan, pythonExample)
395400}
396401
397402func (c * AutonomousCreator ) doFileCreation () (string , error ) {
398- prompt := fmt .Sprintf (`Given the implementation plan:
403+ prompt := fmt .Sprintf (`You are an expert autonomous software engineer.
404+ Given the implementation plan below, generate ALL the necessary code files for this project.
405+
406+ IMPLEMENTATION PLAN:
399407%s
400408
401- Generate all the necessary code files for this project.
409+ CRITICAL RULES:
410+ 1. You MUST create EVERY file and folder described in the plan above. Do not skip any.
411+ 2. If the plan specifies a frontend folder, you MUST generate frontend files inside that folder.
412+ 3. If the plan specifies a backend folder, you MUST generate backend files inside that folder.
413+ 4. All file paths must be RELATIVE to the project root (e.g. "backend/main.go", "frontend/index.html").
414+ 5. Do NOT prefix paths with the project name — files are placed inside the project directory automatically.
415+ 6. Generate complete, working code — not stubs or placeholders.
416+ 7. Follow the EXACT folder structure from the plan.
417+
402418Return the files inside standard Markdown code blocks with the relative filepath specified immediately before the code block.
403419
404- Example:
405- **main.go**
420+ Example format :
421+ **backend/ main.go**
406422` + "```go" + `
407423package main
408- // ...
424+ // full implementation ...
409425` + "```" + `
410426
411- **utils/helper.go **
412- ` + "```go " + `
413- package utils
414- // ...
427+ **frontend/index.html **
428+ ` + "```html " + `
429+ <!DOCTYPE html>
430+ <!-- full implementation ... -->
415431` + "```" + `
416432
417433Only return the file paths and code blocks. No other text.` , c .Plan )
@@ -421,7 +437,45 @@ Only return the file paths and code blocks. No other text.`, c.Plan)
421437 return "" , err
422438 }
423439
424- // Simple parser for "**path/to/file.ext**\n```lang\ncontent\n```"
440+ c .FilesToMake = c .parseFileBlocks (response )
441+
442+ // Write files to disk
443+ createdFiles := c .writeFilesToDisk ()
444+
445+ // Validate structure against the plan and ask AI to fill gaps
446+ missingResult , err := c .validateAndFillStructure (createdFiles )
447+ if err != nil {
448+ // Non-fatal: log but continue
449+ if c .logger != nil {
450+ c .logger .Log ("Structure validation warning: %v" , err )
451+ }
452+ }
453+ if missingResult != "" {
454+ createdFiles = c .getFileList ()
455+ }
456+
457+ // Post-creation dependency resolution for Go projects
458+ projectType := c .detectProjectType ()
459+ if projectType == "Go" {
460+ if err := c .runGoModTidy (); err != nil {
461+ return "" , fmt .Errorf ("failed to run go mod tidy: %v" , err )
462+ }
463+ }
464+
465+ var resultMsg strings.Builder
466+ resultMsg .WriteString (fmt .Sprintf ("ai-assist %s\n Generated and saved %d files:\n - %s\n " , getCurrentTime (), len (createdFiles ), strings .Join (createdFiles , "\n - " )))
467+ if missingResult != "" {
468+ resultMsg .WriteString (missingResult )
469+ }
470+ resultMsg .WriteString ("\n Moving to install dependencies..." )
471+
472+ c .State = StateDependencies
473+ return resultMsg .String (), nil
474+ }
475+
476+ // parseFileBlocks parses the AI response for "**path/to/file.ext**\n```lang\ncontent\n```" blocks.
477+ func (c * AutonomousCreator ) parseFileBlocks (response string ) map [string ]string {
478+ files := make (map [string ]string )
425479 lines := strings .Split (response , "\n " )
426480 var currentFile string
427481 var currentContent strings.Builder
@@ -432,15 +486,20 @@ Only return the file paths and code blocks. No other text.`, c.Plan)
432486
433487 // Check for file name
434488 if ! inBlock && strings .HasPrefix (trimmed , "**" ) && strings .HasSuffix (trimmed , "**" ) {
435- currentFile = strings .Trim (trimmed , "*" )
489+ currentFile = strings .Trim (trimmed , "* " )
490+ // Strip leading project name prefix if the AI accidentally included it
491+ // e.g. "ricardo/backend/main.go" -> "backend/main.go"
492+ if c .ProjectName != "" && strings .HasPrefix (currentFile , c .ProjectName + "/" ) {
493+ currentFile = strings .TrimPrefix (currentFile , c .ProjectName + "/" )
494+ }
436495 continue
437496 }
438497
439498 if strings .HasPrefix (trimmed , "```" ) {
440499 if inBlock {
441500 // End of block
442501 if currentFile != "" {
443- c . FilesToMake [currentFile ] = currentContent .String ()
502+ files [currentFile ] = currentContent .String ()
444503 }
445504 currentFile = ""
446505 currentContent .Reset ()
@@ -456,37 +515,106 @@ Only return the file paths and code blocks. No other text.`, c.Plan)
456515 currentContent .WriteString (line + "\n " )
457516 }
458517 }
518+ return files
519+ }
459520
460- // Write files to disk
521+ // writeFilesToDisk writes all files in FilesToMake to the project directory.
522+ func (c * AutonomousCreator ) writeFilesToDisk () []string {
461523 createdFiles := []string {}
462524 for relPath , content := range c .FilesToMake {
463525 // Port 5000 is blocked on Windows (firewall) and macOS Monterey+ (AirPlay).
464- // Rewrite it to 8080 in all generated files.
465526 content = strings .ReplaceAll (content , "port=5000" , "port=8080" )
466527 content = strings .ReplaceAll (content , "port = 5000" , "port = 8080" )
467528 content = strings .ReplaceAll (content , ":5000" , ":8080" )
468529 c .FilesToMake [relPath ] = content
469530
470531 absPath := filepath .Join (c .ProjectDir , relPath )
471- // Ensure parent dirs exist
472532 os .MkdirAll (filepath .Dir (absPath ), 0755 )
473533
474534 if err := os .WriteFile (absPath , []byte (content ), 0644 ); err != nil {
475- return "" , fmt .Errorf ("failed to write %s: %v" , relPath , err )
535+ if c .logger != nil {
536+ c .logger .Log ("Failed to write %s: %v" , relPath , err )
537+ }
538+ continue
476539 }
477540 createdFiles = append (createdFiles , relPath )
478541 }
542+ return createdFiles
543+ }
479544
480- // Post-creation dependency resolution for Go projects
481- projectType := c .detectProjectType ()
482- if projectType == "Go" {
483- if err := c .runGoModTidy (); err != nil {
484- return "" , fmt .Errorf ("failed to run go mod tidy: %v" , err )
545+ // getFileList returns a sorted list of all file paths in FilesToMake.
546+ func (c * AutonomousCreator ) getFileList () []string {
547+ list := make ([]string , 0 , len (c .FilesToMake ))
548+ for f := range c .FilesToMake {
549+ list = append (list , f )
550+ }
551+ return list
552+ }
553+
554+ // validateAndFillStructure asks the AI to compare the generated files against the plan
555+ // and generates any missing files. Returns a status message and error.
556+ func (c * AutonomousCreator ) validateAndFillStructure (createdFiles []string ) (string , error ) {
557+ fileList := strings .Join (createdFiles , "\n " )
558+
559+ prompt := fmt .Sprintf (`You are an expert software engineer validating a project structure.
560+
561+ IMPLEMENTATION PLAN:
562+ %s
563+
564+ FILES ACTUALLY CREATED:
565+ %s
566+
567+ Compare the plan against the files that were created. Identify ANY files or folders that the plan describes but are MISSING from the created list.
568+
569+ If ALL files from the plan are present, respond with exactly:
570+ STRUCTURE_OK
571+
572+ If files are missing, generate the missing files. Return them in this format:
573+
574+ MISSING_FILES:
575+ **<relative-path>**
576+ ` + "```<lang>" + `
577+ <complete file content>
578+ ` + "```" + `
579+
580+ Only output STRUCTURE_OK or the missing files. No other text.` , c .Plan , fileList )
581+
582+ response , err := aicall (c .AIClient , c .Model , prompt )
583+ if err != nil {
584+ return "" , err
585+ }
586+
587+ trimmed := strings .TrimSpace (response )
588+ if strings .HasPrefix (trimmed , "STRUCTURE_OK" ) {
589+ return "" , nil
590+ }
591+
592+ // Parse and write missing files
593+ missingFiles := c .parseFileBlocks (response )
594+ if len (missingFiles ) == 0 {
595+ return "" , nil
596+ }
597+
598+ var result strings.Builder
599+ result .WriteString (fmt .Sprintf ("\n ai-assist %s\n Structure validation found %d missing files — generating them now:\n " , getCurrentTime (), len (missingFiles )))
600+
601+ for relPath , content := range missingFiles {
602+ content = strings .ReplaceAll (content , ":5000" , ":8080" )
603+ c .FilesToMake [relPath ] = content
604+
605+ absPath := filepath .Join (c .ProjectDir , relPath )
606+ os .MkdirAll (filepath .Dir (absPath ), 0755 )
607+
608+ if err := os .WriteFile (absPath , []byte (content ), 0644 ); err != nil {
609+ if c .logger != nil {
610+ c .logger .Log ("Failed to write missing file %s: %v" , relPath , err )
611+ }
612+ continue
485613 }
614+ result .WriteString (fmt .Sprintf ("- %s\n " , relPath ))
486615 }
487616
488- c .State = StateDependencies
489- return fmt .Sprintf ("ai-assist %s\n Generated and saved %d files:\n - %s\n \n Moving to install dependencies..." , getCurrentTime (), len (createdFiles ), strings .Join (createdFiles , "\n - " )), nil
617+ return result .String (), nil
490618}
491619
492620
@@ -977,6 +1105,25 @@ func (c *AutonomousCreator) buildCodeContext() string {
9771105 return sb .String ()
9781106}
9791107
1108+ // buildCodeContextFromDisk re-reads all known project files from disk so the AI
1109+ // sees the latest state (including any fixes applied by the fallback fixer).
1110+ func (c * AutonomousCreator ) buildCodeContextFromDisk () string {
1111+ var sb strings.Builder
1112+ for relPath := range c .FilesToMake {
1113+ absPath := filepath .Join (c .ProjectDir , relPath )
1114+ data , err := os .ReadFile (absPath )
1115+ if err != nil {
1116+ // Fall back to in-memory content
1117+ sb .WriteString (fmt .Sprintf ("--- %s ---\n %s\n \n " , relPath , c .FilesToMake [relPath ]))
1118+ continue
1119+ }
1120+ content := string (data )
1121+ c .FilesToMake [relPath ] = content // update in-memory map
1122+ sb .WriteString (fmt .Sprintf ("--- %s ---\n %s\n \n " , relPath , content ))
1123+ }
1124+ return sb .String ()
1125+ }
1126+
9801127// runShellCmd executes a shell command in the project directory and returns output.
9811128func (c * AutonomousCreator ) runShellCmd (cmdStr string ) ([]byte , error ) {
9821129 var cmd * exec.Cmd
@@ -993,26 +1140,29 @@ func (c *AutonomousCreator) runShellCmd(cmdStr string) ([]byte, error) {
9931140// It performs up to 3 fix attempts. Returns a log of what happened or an error if
9941141// all attempts fail.
9951142func (c * AutonomousCreator ) aiDrivenFix (failedCmd , errorOutput , context string ) (string , error ) {
996- codeCtx := c .buildCodeContext ()
9971143 var result strings.Builder
9981144 maxAttempts := 3
9991145
10001146 for attempt := 1 ; attempt <= maxAttempts ; attempt ++ {
1147+ // Re-read files from disk each attempt so we have the latest state
1148+ codeCtx := c .buildCodeContextFromDisk ()
1149+
10011150 if c .logger != nil {
10021151 c .logger .Log ("Fix attempt %d/%d for %s error" , attempt , maxAttempts , context )
10031152 }
10041153 result .WriteString (fmt .Sprintf ("ai-assist %s\n Fix attempt %d/%d for %s error...\n " , getCurrentTime (), attempt , maxAttempts , context ))
10051154
10061155 // Ask AI to diagnose and fix
1007- prompt := fmt .Sprintf (`A %s error occurred while running: %s
1156+ prompt := fmt .Sprintf (`You are an expert software engineer debugging a project.
1157+ A %s error occurred while running: %s
10081158
10091159Error output:
10101160%s
10111161
1012- Here are the project files:
1162+ Here are the current project files:
10131163%s
10141164
1015- Analyze the error and provide fixes. Return your response in EXACTLY this format:
1165+ Analyze the error carefully and provide fixes. Return your response in EXACTLY this format:
10161166
10171167For each file that needs changing:
10181168FIX_FILE: <relative path>
@@ -1028,7 +1178,8 @@ UNFIXABLE: <explanation>
10281178Rules:
10291179- Provide the COMPLETE file content, not just the changed lines.
10301180- Do NOT wrap content in markdown code fences.
1031- - Base fixes on the actual error message and code.` , context , failedCmd , errorOutput , codeCtx )
1181+ - Base fixes on the actual error message and code.
1182+ - If the error is about missing files or wrong paths, create the correct files.` , context , failedCmd , errorOutput , codeCtx )
10321183
10331184 fixResponse , err := aicall (c .AIClient , c .Model , prompt )
10341185 if err != nil {
0 commit comments