refactor(recipe): implement one-file-at-a-time refactoring handler

Add refactorFiles to process discovered files individually, generating small JSON responses per file to avoid truncation. Update handleApplyStep to collect and parse multiple single-file JSONs into a unified patch. Switch discoverFiles comment to reflect real scanning. Add fallback default in Run for other steps.
This commit is contained in:
Greg Gauthier 2026-03-06 22:57:07 +00:00
parent 0b5aa5eb57
commit a8208da4c1

View File

@ -37,11 +37,16 @@ func (r *Runner) Run() error {
previousResults = append(previousResults, "Discovered files:\n"+result) previousResults = append(previousResults, "Discovered files:\n"+result)
fmt.Println(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"): case strings.Contains(titleLower, "apply") || strings.Contains(titleLower, "patch"):
r.handleApplyStep(previousResults) r.handleApplyStep(previousResults)
continue continue
default: default:
// fallback for any other step
prompt := fmt.Sprintf(`Recipe Overview: prompt := fmt.Sprintf(`Recipe Overview:
%s %s
@ -76,7 +81,7 @@ Execute this step now. Respond ONLY with the expected output format — no expla
return nil 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 { func (r *Runner) discoverFiles() []string {
var files []string var files []string
root := "internal" root := "internal"
@ -98,6 +103,50 @@ func (r *Runner) discoverFiles() []string {
return files 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 { type FileChange struct {
File string `json:"file"` File string `json:"file"`
Content string `json:"content"` Content string `json:"content"`
@ -109,32 +158,30 @@ func (r *Runner) handleApplyStep(previousResults []string) {
return 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 var ch FileChange
start := strings.Index(lastResult, "[") if err := json.Unmarshal([]byte(jsonStr), &ch); err == nil {
end := strings.LastIndex(lastResult, "]") + 1 allChanges = append(allChanges, ch)
if start == -1 || end == 0 { }
fmt.Println(" ⚠️ No JSON found in previous step — skipping.")
return
} }
jsonStr := lastResult[start:end] if len(allChanges) == 0 {
fmt.Println(" ⚠️ No valid file changes found — skipping.")
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.")
return return
} }
fmt.Println(" 📄 Dry-run mode: creating patch file...") fmt.Println(" 📄 Dry-run mode: creating patch file...")
patchPath := filepath.Join(".", "recipe-refactor.patch") 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) fmt.Printf(" ❌ Failed to create patch: %v\n", err)
return return
} }