diff --git a/internal/recipe/runner.go b/internal/recipe/runner.go index c83eccc..d5aa117 100644 --- a/internal/recipe/runner.go +++ b/internal/recipe/runner.go @@ -37,11 +37,16 @@ func (r *Runner) Run() error { previousResults = append(previousResults, "Discovered files:\n"+result) fmt.Println(result) + case strings.Contains(titleLower, "refactor"): + r.refactorFiles(previousResults) // <-- new one-file-at-a-time handler + continue + case strings.Contains(titleLower, "apply") || strings.Contains(titleLower, "patch"): r.handleApplyStep(previousResults) continue default: + // fallback for any other step prompt := fmt.Sprintf(`Recipe Overview: %s @@ -76,7 +81,7 @@ Execute this step now. Respond ONLY with the expected output format — no expla return nil } -// discoverFiles — temporary .go hard-code (we'll generalize with extensions next) +// discoverFiles — real filesystem scan (we'll generalize with extensions next) func (r *Runner) discoverFiles() []string { var files []string root := "internal" @@ -98,6 +103,50 @@ func (r *Runner) discoverFiles() []string { return files } +// refactorFiles — one file at a time (small JSON, no truncation) +func (r *Runner) refactorFiles(previousResults []string) { + discoveredLine := previousResults[len(previousResults)-1] + lines := strings.Split(discoveredLine, "\n") + + for _, line := range lines { + filePath := strings.TrimSpace(line) + if filePath == "" || strings.HasPrefix(filePath, "Discovered") || filePath == "No files found matching the criteria." { + continue + } + + fmt.Printf(" Refactoring %s...\n", filePath) + + content, err := os.ReadFile(filePath) + if err != nil { + fmt.Printf(" ❌ Could not read %s\n", filePath) + continue + } + + prompt := fmt.Sprintf(`Refactor the following file to use Result[T] instead of naked errors. +Follow existing style and preserve all comments. +Return ONLY this exact JSON (no extra text, no markdown): + +{ + "file": "%s", + "content": "the complete refactored file here" +} + +Original file: +%s`, filePath, string(content)) + + messages := []map[string]string{ + {"role": "system", "content": "You are Grok, built by xAI. Precise expert programmer and refactoring assistant."}, + {"role": "user", "content": prompt}, + } + + response := r.Client.Stream(messages, r.Model) + fmt.Println() + + // store the JSON response for the apply step + previousResults = append(previousResults, response) + } +} + type FileChange struct { File string `json:"file"` Content string `json:"content"` @@ -109,32 +158,30 @@ func (r *Runner) handleApplyStep(previousResults []string) { return } - lastResult := previousResults[len(previousResults)-1] + // collect all JSON objects from the refactor step(s) + var allChanges []FileChange + for _, res := range previousResults { + start := strings.Index(res, "{") + end := strings.LastIndex(res, "}") + 1 + if start == -1 { + continue + } + jsonStr := res[start:end] - // Find the JSON array in the response - start := strings.Index(lastResult, "[") - end := strings.LastIndex(lastResult, "]") + 1 - if start == -1 || end == 0 { - fmt.Println(" ⚠️ No JSON found in previous step — skipping.") - return + var ch FileChange + if err := json.Unmarshal([]byte(jsonStr), &ch); err == nil { + allChanges = append(allChanges, ch) + } } - jsonStr := lastResult[start:end] - - var changes []FileChange - if err := json.Unmarshal([]byte(jsonStr), &changes); err != nil { - fmt.Printf(" ⚠️ Could not parse JSON: %v\n", err) - return - } - - if len(changes) == 0 { - fmt.Println(" ⚠️ No files to apply — skipping.") + if len(allChanges) == 0 { + fmt.Println(" ⚠️ No valid file changes found — skipping.") return } fmt.Println(" 📄 Dry-run mode: creating patch file...") patchPath := filepath.Join(".", "recipe-refactor.patch") - if err := createUnifiedPatch(changes, patchPath); err != nil { + if err := createUnifiedPatch(allChanges, patchPath); err != nil { fmt.Printf(" ❌ Failed to create patch: %v\n", err) return }