diff --git a/internal/recipe/loader.go b/internal/recipe/loader.go index 2421c7c..c7d960b 100644 --- a/internal/recipe/loader.go +++ b/internal/recipe/loader.go @@ -12,8 +12,8 @@ import ( ) var ( + // stepRe still finds the headings (this one is solid) stepRe = regexp.MustCompile(`(?m)^### Step (\d+): (.+)$`) - subRe = regexp.MustCompile(`(?m)^(\*\*(?:Objective|Instructions|Expected output):\*\*)\s*(.+?)(?:\n\n|\n###|\z)`) ) func Load(path string, userParams map[string]any) (*Recipe, error) { @@ -45,7 +45,7 @@ func Load(path string, userParams map[string]any) (*Recipe, error) { } } - // Render templates + // Render templates (so {{.package_path}} becomes "internal") tpl, err := template.New("recipe").Parse(string(parts[2])) if err != nil { return nil, err @@ -61,7 +61,7 @@ func Load(path string, userParams map[string]any) (*Recipe, error) { r.Overview = strings.TrimSpace(body[:idx]) } - // Extract steps — split-based to guarantee no duplicates + // Extract steps with robust multi-line parsing matches := stepRe.FindAllStringSubmatch(body, -1) for i, m := range matches { stepNum := i + 1 @@ -77,20 +77,42 @@ func Load(path string, userParams map[string]any) (*Recipe, error) { section := body[start:end] step := Step{Number: stepNum, Title: title} - for _, sub := range subRe.FindAllStringSubmatch(section, -1) { - switch sub[1] { + + // Simple, reliable label-based parsing (handles multi-line + blank lines) + labels := []string{"**Objective:**", "**Instructions:**", "**Expected output:**"} + for _, label := range labels { + labelStart := strings.Index(section, label) + if labelStart == -1 { + continue + } + contentStart := labelStart + len(label) + contentEnd := len(section) + + // Find next label or end of section + for _, nextLabel := range labels { + next := strings.Index(section[contentStart:], nextLabel) + if next != -1 { + contentEnd = contentStart + next + break + } + } + + content := strings.TrimSpace(section[contentStart:contentEnd]) + + switch label { case "**Objective:**": - step.Objective = strings.TrimSpace(sub[2]) + step.Objective = content case "**Instructions:**": - step.Instructions = strings.TrimSpace(sub[2]) + step.Instructions = content case "**Expected output:**": - step.Expected = strings.TrimSpace(sub[2]) + step.Expected = content } } + r.Steps = append(r.Steps, step) } - // Final summary = everything after the last step + // Final summary (everything after last step) if len(matches) > 0 { lastMatch := matches[len(matches)-1][0] lastIdx := strings.LastIndex(body, lastMatch)