feature/recipe_implementation #5
@ -12,15 +12,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// stepRe finds every "### Step N: Title" heading
|
||||
stepRe = regexp.MustCompile(`(?m)^### Step (\d+): (.+)$`)
|
||||
|
||||
// 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) {
|
||||
b, err := os.ReadFile(path)
|
||||
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)
|
||||
}
|
||||
|
||||
// Apply defaults from YAML if user didn't supply them
|
||||
// Apply defaults
|
||||
if r.Parameters == nil {
|
||||
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]))
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tpl.Execute(&rendered, params); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
for i, m := range matches {
|
||||
stepNum := i + 1
|
||||
title := m[2]
|
||||
|
||||
// crude but effective sub-section extraction
|
||||
start := strings.Index(body, m[0])
|
||||
end := len(body)
|
||||
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]
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// final summary is everything after the last step
|
||||
lastStepEnd := strings.LastIndex(body, matches[len(matches)-1][0])
|
||||
r.FinalSummaryPrompt = strings.TrimSpace(body[lastStepEnd+len(matches[len(matches)-1][0]):])
|
||||
// Final summary = everything after the last step
|
||||
if len(matches) > 0 {
|
||||
lastMatch := matches[len(matches)-1][0]
|
||||
lastIdx := strings.LastIndex(body, lastMatch)
|
||||
r.FinalSummaryPrompt = strings.TrimSpace(body[lastIdx+len(lastMatch):])
|
||||
}
|
||||
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
@ -25,10 +25,7 @@ func (r *Runner) Run() error {
|
||||
for _, step := range r.Recipe.Steps {
|
||||
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(`You are an expert sous-chef executing a recipe with precision.
|
||||
|
||||
Recipe Overview:
|
||||
prompt := fmt.Sprintf(`Recipe Overview:
|
||||
%s
|
||||
|
||||
Previous step results (for context):
|
||||
@ -39,35 +36,34 @@ Objective: %s
|
||||
Instructions: %s
|
||||
Expected output format: %s
|
||||
|
||||
Execute the step now. Be concise and follow the expected output format exactly.`,
|
||||
r.Recipe.Overview, // we'll extract this too if you want, or leave as-is
|
||||
strings.Join(previousResults, "\n\n"),
|
||||
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)
|
||||
|
||||
// Use the same streaming client as every other command
|
||||
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},
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// Final summary step
|
||||
// Final summary
|
||||
fmt.Println("Final Summary")
|
||||
finalPrompt := fmt.Sprintf(`You just executed the entire recipe. Here is the full history:
|
||||
|
||||
%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{
|
||||
{"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},
|
||||
}
|
||||
r.Client.Stream(messages, r.Model)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user