grokkit/internal/recipe/runner.go
Greg Gauthier c5bdd44e55 fix(recipe): update regex to match Grok's current output style
Adjust the regex in handleApplyStep to flexibly match both old and new Grok formats for code blocks. Also, remove blank import for bufio as it's now used.
2026-03-06 22:21:58 +00:00

155 lines
4.0 KiB
Go

package recipe
import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"gmgauthier.com/grokkit/internal/grok"
)
type Runner struct {
Recipe *Recipe
Client *grok.Client
Model string
}
func NewRunner(r *Recipe, client *grok.Client, model string) *Runner {
return &Runner{Recipe: r, Client: client, Model: model}
}
func (r *Runner) Run() error {
fmt.Printf("🍳 Starting recipe: %s v%s\n\n", r.Recipe.Name, r.Recipe.Version)
var previousResults []string
for _, step := range r.Recipe.Steps {
fmt.Printf("Step %d/%d: %s\n", step.Number, len(r.Recipe.Steps), step.Title)
titleLower := strings.ToLower(step.Title)
switch {
case strings.Contains(titleLower, "discover") || strings.Contains(titleLower, "find"):
files := r.discoverFiles()
result := strings.Join(files, "\n")
previousResults = append(previousResults, "Discovered files:\n"+result)
fmt.Println(result)
case strings.Contains(titleLower, "apply") || strings.Contains(titleLower, "patch"):
r.handleApplyStep(previousResults)
continue
default:
prompt := fmt.Sprintf(`Recipe Overview:
%s
Previous step results (for context):
%s
=== CURRENT STEP ===
Objective: %s
Instructions: %s
Expected output format: %s
Execute this step now. Respond ONLY with the expected output format — no explanations, no extra text.`,
r.Recipe.Overview,
strings.Join(previousResults, "\n\n---\n\n"),
step.Objective,
step.Instructions,
step.Expected)
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()
previousResults = append(previousResults, fmt.Sprintf("Step %d result:\n%s", step.Number, response))
}
}
fmt.Println("\n✅ Recipe complete.")
return nil
}
// discoverFiles does a real filesystem scan
func (r *Runner) discoverFiles() []string {
var files []string
root := "internal"
_ = filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") {
return nil
}
b, _ := os.ReadFile(path)
if strings.Contains(string(b), "if err != nil") {
files = append(files, path)
}
return nil
})
if len(files) == 0 {
files = append(files, "No files found matching the criteria.")
}
return files
}
// handleApplyStep with flexible regex that matches Grok's current output style
func (r *Runner) handleApplyStep(previousResults []string) {
if len(previousResults) == 0 {
fmt.Println(" ⚠️ No previous results to apply — skipping.")
return
}
lastResult := previousResults[len(previousResults)-1]
blocks := extractCodeBlocks(lastResult)
if len(blocks) == 0 {
fmt.Println(" ⚠️ No code blocks found to apply — skipping.")
return
}
fmt.Println(" 📄 Dry-run mode: creating patch file...")
patchPath := filepath.Join(".", "recipe-refactor.patch")
if err := createUnifiedPatch(blocks, patchPath); err != nil {
fmt.Printf(" ❌ Failed to create patch: %v\n", err)
return
}
fmt.Printf(" ✅ Patch created: %s\n", patchPath)
fmt.Println(" Review it, then run with dry_run=false to apply.")
}
// Flexible regex — matches both old and new Grok formats
var blockRe = regexp.MustCompile(`(?s)```go\n(?://\s*)?(.+?\.go)\n(.*?)\n````)
func extractCodeBlocks(text string) map[string]string {
blocks := make(map[string]string)
matches := blockRe.FindAllStringSubmatch(text, -1)
for _, m := range matches {
if len(m) == 3 {
blocks[m[1]] = m[2]
}
}
return blocks
}
func createUnifiedPatch(blocks map[string]string, patchPath string) error {
f, err := os.Create(patchPath)
if err != nil {
return err
}
defer f.Close()
for path, content := range blocks {
fmt.Fprintf(f, "--- %s\n+++ %s\n@@ -0,0 +1,%d @@\n", path, path, strings.Count(content, "\n")+1)
for _, line := range strings.Split(content, "\n") {
fmt.Fprintf(f, "+%s\n", line)
}
}
return nil
}