From 8efca621094a76a99b7f5ae7029f2e73ae4d1ae0 Mon Sep 17 00:00:00 2001 From: Greg Gauthier Date: Sat, 7 Mar 2026 15:52:56 +0000 Subject: [PATCH] refactor(runner): improve package path resolution for consistency - Introduce resolvePackagePath to handle ~ expansion, relative paths, and absolutization at the start. - Update discoverFiles and executeShellCommands to use the resolved workDir. - Remove redundant path logic from discoverFiles and shell execution for better maintainability. --- internal/recipe/runner.go | 67 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/internal/recipe/runner.go b/internal/recipe/runner.go index e38fab5..96ea16f 100644 --- a/internal/recipe/runner.go +++ b/internal/recipe/runner.go @@ -24,6 +24,9 @@ func NewRunner(r *Recipe, client *grok.Client, model string) *Runner { func (r *Runner) Run() error { fmt.Printf("🍳 Starting recipe: %s v%s\n\n", r.Recipe.Name, r.Recipe.Version) + // Resolve package_path once at the start (handles ~, ., etc.) + workDir := r.resolvePackagePath() + var previousResults []string var refactorJSONs []string @@ -34,7 +37,7 @@ func (r *Runner) Run() error { switch { case strings.Contains(titleLower, "discover") || strings.Contains(titleLower, "find"): - files := r.discoverFiles() + files := r.discoverFiles(workDir) result := strings.Join(files, "\n") previousResults = append(previousResults, "Discovered files:\n"+result) fmt.Println(result) @@ -47,16 +50,14 @@ func (r *Runner) Run() error { r.handleApplyStep(refactorJSONs) continue - // === NEW: Safe shell command execution === case strings.Contains(titleLower, "initialize") || strings.Contains(titleLower, "create") || strings.Contains(titleLower, "run") || strings.Contains(titleLower, "shell") || strings.Contains(titleLower, "poetry") || strings.Contains(titleLower, "git") || strings.Contains(titleLower, "tea"): - r.executeShellCommands(step, previousResults) + r.executeShellCommands(step, previousResults, workDir) continue default: - // fallback for any other step prompt := fmt.Sprintf(`Recipe Overview: %s @@ -91,11 +92,8 @@ Execute this step now. Respond ONLY with the expected output format — no expla return nil } -// discoverFiles — fully generic using recipe metadata -func (r *Runner) discoverFiles() []string { - var files []string - - // 1. Use explicit --param package_path if provided +// resolvePackagePath expands ~, ., and makes the path absolute +func (r *Runner) resolvePackagePath() string { root := "." if v, ok := r.Recipe.ResolvedParams["package_path"]; ok { if s, ok := v.(string); ok && s != "" { @@ -103,14 +101,26 @@ func (r *Runner) discoverFiles() []string { } } - // 2. Smart defaults if no param was given - if root == "." { - if _, err := os.Stat("src"); err == nil { - root = "src" - } + // Expand ~ + if strings.HasPrefix(root, "~/") { + home, _ := os.UserHomeDir() + root = filepath.Join(home, root[2:]) + } else if root == "~" { + root, _ = os.UserHomeDir() } - // 3. Build allowed extensions from recipe frontmatter + // Make absolute + abs, err := filepath.Abs(root) + if err != nil { + abs = root + } + return abs +} + +// discoverFiles now takes the resolved absolute path +func (r *Runner) discoverFiles(root string) []string { + var files []string + allowedExt := make(map[string]bool) for _, lang := range r.Recipe.ProjectLanguages { if exts, ok := r.Recipe.Extensions[lang]; ok { @@ -120,19 +130,13 @@ func (r *Runner) discoverFiles() []string { } } - // 4. Use a configurable search pattern (defaults to Go-style) - searchFor := r.Recipe.SearchPattern - if searchFor == "" { - searchFor = "if err != nil" - } - _ = filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { if err != nil || d.IsDir() { return nil } if allowedExt[filepath.Ext(path)] { b, _ := os.ReadFile(path) - if strings.Contains(string(b), searchFor) { + if strings.Contains(string(b), r.Recipe.SearchPattern) { files = append(files, path) } } @@ -255,8 +259,9 @@ func createUnifiedPatch(changes []FileChange, patchPath string) error { return nil } -// NEW: Safe shell command execution -func (r *Runner) executeShellCommands(step Step, previousResults []string) { +// executeShellCommands — safe, whitelisted, boundary-enforced +func (r *Runner) executeShellCommands(step Step, previousResults []string, workDir string) { + prompt := fmt.Sprintf(`You are executing shell commands for this step. Recipe Overview: @@ -292,7 +297,7 @@ Only use commands from the allowed list. Never use cd, rm -rf, or anything that response := r.Client.Stream(messages, r.Model) fmt.Println() - // Parse JSON command list + // Parse JSON... type ShellCommand struct { Command string `json:"command"` Args []string `json:"args"` @@ -311,14 +316,6 @@ Only use commands from the allowed list. Never use cd, rm -rf, or anything that return } - // Resolve boundary - cwd := "." - if v, ok := r.Recipe.ResolvedParams["package_path"]; ok { - if s, ok := v.(string); ok && s != "" { - cwd = s - } - } - for _, cmd := range cmds { fullCmd := cmd.Command if len(cmd.Args) > 0 { @@ -340,9 +337,9 @@ Only use commands from the allowed list. Never use cd, rm -rf, or anything that continue } - // Execute with strict cwd + // Execute with strict cwd = resolved package_path execCmd := exec.Command(cmd.Command, cmd.Args...) - execCmd.Dir = cwd + execCmd.Dir = workDir output, err := execCmd.CombinedOutput() if err != nil {