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.
155 lines
4.0 KiB
Go
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
|
|
} |