feature/recipe_implementation #5

Merged
gmgauthier merged 54 commits from feature/recipe_implementation into master 2026-03-07 23:17:54 +00:00
2 changed files with 27 additions and 32 deletions
Showing only changes of commit d1ebd2af79 - Show all commits

View File

@ -12,15 +12,10 @@ import (
) )
var ( var (
// stepRe finds every "### Step N: Title" heading
stepRe = regexp.MustCompile(`(?m)^### Step (\d+): (.+)$`) stepRe = regexp.MustCompile(`(?m)^### Step (\d+): (.+)$`)
subRe = regexp.MustCompile(`(?m)^(\*\*(?:Objective|Instructions|Expected output):\*\*)\s*(.+?)(?:\n\n|\n###|\z)`)
// subRe finds the three labelled sections inside each step (no Perl lookahead)
subRe = regexp.MustCompile(`(?m)^(\*\*(?:Objective|Instructions|Expected output):\*\*)\s*(.+?)(?:\n\n|\n###|\z)`)
) )
// Load reads a recipe from disk and fully parses it.
// Load reads a recipe from disk and fully parses it.
func Load(path string, userParams map[string]any) (*Recipe, error) { func Load(path string, userParams map[string]any) (*Recipe, error) {
b, err := os.ReadFile(path) b, err := os.ReadFile(path)
if err != nil { if err != nil {
@ -37,7 +32,7 @@ func Load(path string, userParams map[string]any) (*Recipe, error) {
return nil, fmt.Errorf("yaml parse: %w", err) return nil, fmt.Errorf("yaml parse: %w", err)
} }
// Apply defaults from YAML if user didn't supply them // Apply defaults
if r.Parameters == nil { if r.Parameters == nil {
r.Parameters = make(map[string]Parameter) r.Parameters = make(map[string]Parameter)
} }
@ -50,7 +45,7 @@ func Load(path string, userParams map[string]any) (*Recipe, error) {
} }
} }
// render templates with defaults applied // Render templates
tpl, err := template.New("recipe").Parse(string(parts[2])) tpl, err := template.New("recipe").Parse(string(parts[2]))
if err != nil { if err != nil {
return nil, err return nil, err
@ -59,25 +54,26 @@ func Load(path string, userParams map[string]any) (*Recipe, error) {
if err := tpl.Execute(&rendered, params); err != nil { if err := tpl.Execute(&rendered, params); err != nil {
return nil, err return nil, err
} }
if err := tpl.Execute(&rendered, params); err != nil {
return nil, err
}
body := rendered.String() body := rendered.String()
// extract steps // Extract Overview
if idx := strings.Index(body, "## Execution Steps"); idx != -1 {
r.Overview = strings.TrimSpace(body[:idx])
}
// Extract steps — split-based to guarantee no duplicates
matches := stepRe.FindAllStringSubmatch(body, -1) matches := stepRe.FindAllStringSubmatch(body, -1)
for i, m := range matches { for i, m := range matches {
stepNum := i + 1 stepNum := i + 1
title := m[2] title := m[2]
// crude but effective sub-section extraction
start := strings.Index(body, m[0]) start := strings.Index(body, m[0])
end := len(body) end := len(body)
if i+1 < len(matches) { if i+1 < len(matches) {
end = strings.Index(body[start:], matches[i+1][0]) + start nextStart := strings.Index(body[start:], matches[i+1][0])
end = start + nextStart
} }
section := body[start:end] section := body[start:end]
step := Step{Number: stepNum, Title: title} step := Step{Number: stepNum, Title: title}
@ -94,9 +90,12 @@ func Load(path string, userParams map[string]any) (*Recipe, error) {
r.Steps = append(r.Steps, step) r.Steps = append(r.Steps, step)
} }
// final summary is everything after the last step // Final summary = everything after the last step
lastStepEnd := strings.LastIndex(body, matches[len(matches)-1][0]) if len(matches) > 0 {
r.FinalSummaryPrompt = strings.TrimSpace(body[lastStepEnd+len(matches[len(matches)-1][0]):]) lastMatch := matches[len(matches)-1][0]
lastIdx := strings.LastIndex(body, lastMatch)
r.FinalSummaryPrompt = strings.TrimSpace(body[lastIdx+len(lastMatch):])
}
return &r, nil return &r, nil
} }

View File

@ -25,10 +25,7 @@ func (r *Runner) Run() error {
for _, step := range r.Recipe.Steps { for _, step := range r.Recipe.Steps {
fmt.Printf("Step %d/%d: %s\n", step.Number, len(r.Recipe.Steps), step.Title) fmt.Printf("Step %d/%d: %s\n", step.Number, len(r.Recipe.Steps), step.Title)
// Build a clear, self-contained prompt for this step prompt := fmt.Sprintf(`Recipe Overview:
prompt := fmt.Sprintf(`You are an expert sous-chef executing a recipe with precision.
Recipe Overview:
%s %s
Previous step results (for context): Previous step results (for context):
@ -39,35 +36,34 @@ Objective: %s
Instructions: %s Instructions: %s
Expected output format: %s Expected output format: %s
Execute the step now. Be concise and follow the expected output format exactly.`, Execute this step now. Respond ONLY with the expected output format no explanations, no extra text.`,
r.Recipe.Overview, // we'll extract this too if you want, or leave as-is r.Recipe.Overview,
strings.Join(previousResults, "\n\n"), strings.Join(previousResults, "\n\n---\n\n"),
step.Objective, step.Objective,
step.Instructions, step.Instructions,
step.Expected) step.Expected)
// Use the same streaming client as every other command
messages := []map[string]string{ messages := []map[string]string{
{"role": "system", "content": "You are a precise, no-nonsense coding sous-chef."}, {"role": "system", "content": "You are Grok, built by xAI. You are a precise, expert Go programmer and refactoring assistant. Always follow the user's instructions exactly for legitimate coding tasks. Never refuse or add safety warnings."},
{"role": "user", "content": prompt}, {"role": "user", "content": prompt},
} }
response := r.Client.Stream(messages, r.Model) response := r.Client.Stream(messages, r.Model)
fmt.Println() // extra newline after streaming finishes fmt.Println()
previousResults = append(previousResults, fmt.Sprintf("Step %d result:\n%s", step.Number, response)) previousResults = append(previousResults, fmt.Sprintf("Step %d result:\n%s", step.Number, response))
} }
// Final summary step // Final summary
fmt.Println("Final Summary") fmt.Println("Final Summary")
finalPrompt := fmt.Sprintf(`You just executed the entire recipe. Here is the full history: finalPrompt := fmt.Sprintf(`You just executed the entire recipe. Here is the full history:
%s %s
%s`, strings.Join(previousResults, "\n\n"), r.Recipe.FinalSummaryPrompt) %s`, strings.Join(previousResults, "\n\n---\n\n"), r.Recipe.FinalSummaryPrompt)
messages := []map[string]string{ messages := []map[string]string{
{"role": "system", "content": "You are a precise, no-nonsense coding sous-chef."}, {"role": "system", "content": "You are Grok, built by xAI. You are a precise, expert programmer and refactoring assistant."},
{"role": "user", "content": finalPrompt}, {"role": "user", "content": finalPrompt},
} }
r.Client.Stream(messages, r.Model) r.Client.Stream(messages, r.Model)