package singlerequesttemplate import ( "crypto/sha256" "encoding/hex" "errors" "fmt" "regexp" "strings" "unicode/utf8" ) const MaxTemplateBytes = 8192 const DefaultPlanTemplate = `# Plan ## Goal {{goal}} ## Steps {{steps}} ## Verification {{verification}} ` const DefaultReviewTemplate = `# Review ## Worker Item Status {{item_status}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} ` var ( ErrInvalidTemplate = errors.New("single-request template: invalid template") ErrTemplateTooLarge = errors.New("single-request template: exceeds maximum size") ErrMalformedPlan = errors.New("single-request template: malformed plan output") ErrMalformedReview = errors.New("single-request template: malformed review fields") ) var placeholderRegex = regexp.MustCompile(`\{\{[^}]*\}\}`) var ( planPlaceholders = []string{"{{goal}}", "{{steps}}", "{{verification}}"} planHeadings = []string{"# Plan", "## Goal", "## Steps", "## Verification"} reviewPlaceholders = []string{"{{item_status}}", "{{changes}}", "{{verification}}", "{{deviations}}"} reviewHeadings = []string{"# Review", "## Worker Item Status", "## Worker Changes", "## Worker Verification", "## Deviations"} ) type ReviewFields struct { ItemStatus string Changes string Verification string Deviations string } type PlanFields struct { Goal string Steps []string Verification []string } // exactLineOffsets returns the byte offset of the first standalone line equal to // want and how many standalone lines matched. A standalone line is a maximal // "\n"-delimited segment compared byte for byte, so decorated variants such as // "### Plan", "# Plan Mismatch", or "NOTPASS" never satisfy a required line. func exactLineOffsets(tmpl, want string) (int, int) { index, count, offset := -1, 0, 0 for { var line string end := strings.IndexByte(tmpl[offset:], '\n') if end < 0 { line = tmpl[offset:] } else { line = tmpl[offset : offset+end] } if line == want { count++ if index < 0 { index = offset } } if end < 0 { return index, count } offset += end + 1 } } // requireExactLines resolves every required standalone line, rejecting missing // and duplicated occurrences, and returns their offsets in the requested order. // Only the documented required line is echoed on failure; template content is // never included in the error. func requireExactLines(tmpl string, lines []string) ([]int, error) { offsets := make([]int, len(lines)) for i, line := range lines { index, count := exactLineOffsets(tmpl, line) if count != 1 { return nil, fmt.Errorf("%w: must contain the standalone line %q exactly once", ErrInvalidTemplate, line) } offsets[i] = index } return offsets, nil } // requirePlaceholderInventory closes the placeholder grammar. Each documented // placeholder must appear exactly once; after removing those exact occurrences // no template delimiter may survive, so unknown placeholders and unbalanced // "{{"/"}}" residue are both rejected. Only documented placeholder names are // echoed on failure; template content is never included in the error. func requirePlaceholderInventory(tmpl string, placeholders []string) ([]int, error) { offsets := make([]int, len(placeholders)) residue := tmpl for i, placeholder := range placeholders { if strings.Count(tmpl, placeholder) != 1 { return nil, fmt.Errorf("%w: must contain %s exactly once", ErrInvalidTemplate, placeholder) } offsets[i] = strings.Index(tmpl, placeholder) residue = strings.Replace(residue, placeholder, "", 1) } if placeholderRegex.MatchString(residue) { return nil, fmt.Errorf("%w: template declares an unknown placeholder", ErrInvalidTemplate) } if strings.Contains(residue, "{{") || strings.Contains(residue, "}}") { return nil, fmt.Errorf("%w: template leaves an unbalanced placeholder delimiter", ErrInvalidTemplate) } return offsets, nil } func ascending(values ...int) bool { for i := 1; i < len(values); i++ { if values[i-1] >= values[i] { return false } } return true } func Digest(content string) string { h := sha256.Sum256([]byte(content)) return hex.EncodeToString(h[:]) } func ValidatePlanTemplate(tmpl string) error { if len(tmpl) == 0 { return fmt.Errorf("%w: template is empty", ErrInvalidTemplate) } if len(tmpl) > MaxTemplateBytes { return fmt.Errorf("%w: template size %d exceeds max %d", ErrTemplateTooLarge, len(tmpl), MaxTemplateBytes) } placeholders, err := requirePlaceholderInventory(tmpl, planPlaceholders) if err != nil { return err } idxGoal, idxSteps, idxVerif := placeholders[0], placeholders[1], placeholders[2] if !ascending(idxGoal, idxSteps, idxVerif) { return fmt.Errorf("%w: placeholders must appear in order {{goal}}, {{steps}}, {{verification}}", ErrInvalidTemplate) } headings, err := requireExactLines(tmpl, planHeadings) if err != nil { return err } idxPlanH, idxGoalH, idxStepsH, idxVerifH := headings[0], headings[1], headings[2], headings[3] if !ascending(idxPlanH, idxGoalH, idxGoal, idxStepsH, idxSteps, idxVerifH, idxVerif) { return fmt.Errorf("%w: headings and placeholders must follow exact structural order", ErrInvalidTemplate) } return nil } func ValidateReviewTemplate(tmpl string) error { if len(tmpl) == 0 { return fmt.Errorf("%w: template is empty", ErrInvalidTemplate) } if len(tmpl) > MaxTemplateBytes { return fmt.Errorf("%w: template size %d exceeds max %d", ErrTemplateTooLarge, len(tmpl), MaxTemplateBytes) } placeholders, err := requirePlaceholderInventory(tmpl, reviewPlaceholders) if err != nil { return err } idxItemStatus, idxChanges, idxVerif, idxDeviations := placeholders[0], placeholders[1], placeholders[2], placeholders[3] if !ascending(idxItemStatus, idxChanges, idxVerif, idxDeviations) { return fmt.Errorf("%w: placeholders must appear in order {{item_status}}, {{changes}}, {{verification}}, {{deviations}}", ErrInvalidTemplate) } headings, err := requireExactLines(tmpl, reviewHeadings) if err != nil { return err } idxReviewH, idxItemStatusH, idxChangesH, idxVerifH, idxDeviationsH := headings[0], headings[1], headings[2], headings[3], headings[4] if !ascending(idxReviewH, idxItemStatusH, idxItemStatus, idxChangesH, idxChanges, idxVerifH, idxVerif, idxDeviationsH, idxDeviations) { return fmt.Errorf("%w: headings and placeholders must follow exact structural order", ErrInvalidTemplate) } // Close the heading set: only the documented worker headings may appear. A // reviewer-only section (Result, Checks, Summary) or any other markdown // heading would let the template describe a reviewer verdict or final review // page, so it is rejected rather than silently tolerated. if err := rejectUnknownMarkdownHeadings(tmpl, reviewHeadings); err != nil { return err } return nil } // rejectUnknownMarkdownHeadings ensures every standalone line beginning with // "#" is one of the allowed documented headings. Decorated variants (e.g. // "### Review") and reviewer-only headings ("## Result") are rejected because // the only standalone matching already happens in requireExactLines; here we // additionally forbid any extra heading that is not in the closed set. func rejectUnknownMarkdownHeadings(tmpl string, allowed []string) error { allowedSet := make(map[string]struct{}, len(allowed)) for _, line := range allowed { allowedSet[line] = struct{}{} } offset := 0 for { var line string end := strings.IndexByte(tmpl[offset:], '\n') if end < 0 { line = tmpl[offset:] } else { line = tmpl[offset : offset+end] } if strings.HasPrefix(line, "#") { if _, ok := allowedSet[line]; !ok { return fmt.Errorf("%w: template declares an unknown heading", ErrInvalidTemplate) } } if end < 0 { return nil } offset += end + 1 } } func ParsePlan(tmpl string, rawOutput string, maxOutputBytes int) ([]byte, error) { if maxOutputBytes < 1 || len(rawOutput) > maxOutputBytes { return nil, ErrMalformedPlan } if err := ValidatePlanTemplate(tmpl); err != nil { return nil, err } if strings.Contains(rawOutput, "{{") || strings.Contains(rawOutput, "}}") { return nil, ErrMalformedPlan } idxGoalPlaceholder := strings.Index(tmpl, "{{goal}}") idxStepsPlaceholder := strings.Index(tmpl, "{{steps}}") idxVerifPlaceholder := strings.Index(tmpl, "{{verification}}") f0 := tmpl[:idxGoalPlaceholder] f1 := tmpl[idxGoalPlaceholder+len("{{goal}}") : idxStepsPlaceholder] f2 := tmpl[idxStepsPlaceholder+len("{{steps}}") : idxVerifPlaceholder] f3 := tmpl[idxVerifPlaceholder+len("{{verification}}"):] if !strings.HasPrefix(rawOutput, f0) { return nil, ErrMalformedPlan } rem := rawOutput[len(f0):] i1 := strings.Index(rem, f1) if i1 < 0 { return nil, ErrMalformedPlan } vGoal := rem[:i1] rem = rem[i1+len(f1):] i2 := strings.Index(rem, f2) if i2 < 0 { return nil, ErrMalformedPlan } vSteps := rem[:i2] rem = rem[i2+len(f2):] var vVerif string if f3 == "" { vVerif = rem } else { suffix := f3 if !strings.HasSuffix(rem, suffix) { // Provider chat APIs commonly omit the model's final line feed. Treat // only that last byte as optional; all other static suffix text must // still match the configured template exactly. if !strings.HasSuffix(f3, "\n") { return nil, ErrMalformedPlan } suffix = strings.TrimSuffix(f3, "\n") if !strings.HasSuffix(rem, suffix) { return nil, ErrMalformedPlan } } vVerif = rem[:len(rem)-len(suffix)] } if _, _, _, err := normalizePlanSections(vGoal, vSteps, vVerif); err != nil { return nil, err } return []byte(rawOutput), nil } func normalizePlanSections(goal, steps, verification string) (string, string, string, error) { goal = strings.TrimSpace(goal) steps = strings.TrimSpace(steps) verification = strings.TrimSpace(verification) if goal == "" || strings.ContainsAny(goal, "\r\n") || steps == "" || verification == "" { return "", "", "", ErrMalformedPlan } if strings.Contains(goal, "{{") || strings.Contains(goal, "}}") || strings.Contains(steps, "{{") || strings.Contains(steps, "}}") || strings.Contains(verification, "{{") || strings.Contains(verification, "}}") { return "", "", "", ErrMalformedPlan } normalizeStepBullets := func(value string, minimum, maximum int) (string, error) { lines := strings.Split(value, "\n") if len(lines) < minimum || len(lines) > maximum { return "", ErrMalformedPlan } for i, line := range lines { line = strings.TrimSpace(line) prefix := fmt.Sprintf("- [P%d] ", i+1) if !strings.HasPrefix(line, prefix) || strings.TrimSpace(line[len(prefix):]) == "" { return "", ErrMalformedPlan } lines[i] = line } return strings.Join(lines, "\n"), nil } normalizeVerificationBullets := func(value string, minimum, maximum int) (string, error) { lines := strings.Split(value, "\n") if len(lines) < minimum || len(lines) > maximum { return "", ErrMalformedPlan } for i, line := range lines { line = strings.TrimSpace(line) if !strings.HasPrefix(line, "- ") || strings.TrimSpace(line[2:]) == "" { return "", ErrMalformedPlan } lines[i] = line } return strings.Join(lines, "\n"), nil } steps, err := normalizeStepBullets(steps, 2, 6) if err != nil { return "", "", "", err } verification, err = normalizeVerificationBullets(verification, 1, 3) if err != nil { return "", "", "", err } return goal, steps, verification, nil } func normalizePlanFields(fields PlanFields) (string, string, string, error) { goal := strings.TrimSpace(fields.Goal) if goal == "" || strings.ContainsAny(goal, "\r\n") || strings.Contains(goal, "{{") || strings.Contains(goal, "}}") { return "", "", "", ErrMalformedPlan } normalizeStepItems := func(items []string, minimum, maximum int) (string, error) { if len(items) < minimum || len(items) > maximum { return "", ErrMalformedPlan } lines := make([]string, len(items)) for i, item := range items { item = strings.TrimSpace(item) if item == "" || strings.ContainsAny(item, "\r\n") || strings.Contains(item, "{{") || strings.Contains(item, "}}") { return "", ErrMalformedPlan } lines[i] = fmt.Sprintf("- [P%d] %s", i+1, item) } return strings.Join(lines, "\n"), nil } normalizeVerificationItems := func(items []string, minimum, maximum int) (string, error) { if len(items) < minimum || len(items) > maximum { return "", ErrMalformedPlan } lines := make([]string, len(items)) for i, item := range items { item = strings.TrimSpace(item) if item == "" || strings.ContainsAny(item, "\r\n") || strings.Contains(item, "{{") || strings.Contains(item, "}}") { return "", ErrMalformedPlan } lines[i] = "- " + item } return strings.Join(lines, "\n"), nil } steps, err := normalizeStepItems(fields.Steps, 2, 6) if err != nil { return "", "", "", err } verification, err := normalizeVerificationItems(fields.Verification, 1, 3) if err != nil { return "", "", "", err } return goal, steps, verification, nil } func RenderPlan(tmpl string, fields PlanFields, maxOutputBytes int) ([]byte, error) { if maxOutputBytes < 1 { return nil, ErrMalformedPlan } if err := ValidatePlanTemplate(tmpl); err != nil { return nil, err } goal, steps, verification, err := normalizePlanFields(fields) if err != nil { return nil, err } res := strings.ReplaceAll(tmpl, "{{goal}}", goal) res = strings.ReplaceAll(res, "{{steps}}", steps) res = strings.ReplaceAll(res, "{{verification}}", verification) if strings.Contains(res, "{{") || strings.Contains(res, "}}") || len(res) > maxOutputBytes { return nil, ErrMalformedPlan } return []byte(res), nil } func RenderReview(tmpl string, fields ReviewFields, maxOutputBytes int) ([]byte, error) { if maxOutputBytes < 1 { return nil, ErrMalformedReview } if err := ValidateReviewTemplate(tmpl); err != nil { return nil, err } itemStatus := strings.TrimSpace(fields.ItemStatus) changes := strings.TrimSpace(fields.Changes) verification := strings.TrimSpace(fields.Verification) deviations := strings.TrimSpace(fields.Deviations) if itemStatus == "" || changes == "" || verification == "" || deviations == "" { return nil, ErrMalformedReview } res := strings.ReplaceAll(tmpl, "{{item_status}}", itemStatus) res = strings.ReplaceAll(res, "{{changes}}", changes) res = strings.ReplaceAll(res, "{{verification}}", verification) res = strings.ReplaceAll(res, "{{deviations}}", deviations) if strings.Contains(res, "{{") || strings.Contains(res, "}}") { return nil, ErrMalformedReview } if len(res) > maxOutputBytes { return nil, ErrMalformedReview } return []byte(res), nil } var planStepIDRegex = regexp.MustCompile(`(?m)^- \[P(\d+)\]`) // PlanItemIDs extracts the deterministic P1..Pn step IDs from a rendered Plan // document. IDs must start at P1 and increment with no gaps, duplicates, or // out-of-order entries. func PlanItemIDs(plan []byte) ([]string, error) { if len(plan) == 0 || !utf8.Valid(plan) { return nil, ErrMalformedPlan } text := string(plan) for _, heading := range planHeadings { if _, count := exactLineOffsets(text, heading); count != 1 { return nil, ErrMalformedPlan } } goalStart := strings.Index(text, "## Goal") + len("## Goal") stepsHeading := strings.Index(text, "## Steps") stepsStart := stepsHeading + len("## Steps") verificationHeading := strings.Index(text, "## Verification") if goalStart < len("## Goal") || stepsHeading < 0 || verificationHeading < 0 || goalStart >= stepsHeading || stepsStart >= verificationHeading || strings.TrimSpace(text[goalStart:stepsHeading]) == "" || strings.TrimSpace(text[verificationHeading+len("## Verification"):]) == "" { return nil, ErrMalformedPlan } steps := strings.TrimSpace(text[stepsStart:verificationHeading]) lines := strings.Split(steps, "\n") if len(lines) < 2 || len(lines) > 6 { return nil, ErrMalformedPlan } matches := planStepIDRegex.FindAllStringSubmatch(steps, -1) if len(matches) != len(lines) { return nil, ErrMalformedPlan } ids := make([]string, 0, len(matches)) for i, match := range matches { expected := fmt.Sprintf("P%d", i+1) actual := "P" + match[1] line := strings.TrimSpace(lines[i]) prefix := fmt.Sprintf("- [%s] ", expected) if actual != expected || !strings.HasPrefix(line, prefix) || strings.TrimSpace(line[len(prefix):]) == "" { return nil, ErrMalformedPlan } ids = append(ids, expected) } return ids, nil } var reviewItemLineRegex = regexp.MustCompile(`(?m)^- (P\d+): (.+)$`) // ValidateReviewHandoff validates a rendered REVIEW handoff document against // the supplied plan IDs. Every plan ID must appear exactly once in the Worker // Item Status section with status "completed", and no unknown or duplicate // IDs are permitted. All four required sections must be present with // non-empty content. If the PLAN has no deviations, the Deviations section // must still contain an explicit entry (conventionally "None"). // // The Worker Item Status section is validated by exact line inventory rather // than regex filtering: the entire section (excluding the heading) is split on // newlines, every resulting line must be non-empty, and each line must match // its corresponding plan ID in the form "- Pn: completed". This rejects prose // injected between status lines, blank lines, malformed bullets, and any // out-of-order or duplicate entries in a single pass. func ValidateReviewHandoff(content []byte, planIDs []string) error { if len(content) == 0 || !utf8.Valid(content) || len(planIDs) == 0 { return ErrMalformedReview } text := string(content) requiredSections := []string{"# Review", "## Worker Item Status", "## Worker Changes", "## Worker Verification", "## Deviations"} for _, section := range requiredSections { if _, count := exactLineOffsets(text, section); count != 1 { return ErrMalformedReview } } if err := rejectUnknownMarkdownHeadings(text, reviewHeadings); err != nil { return ErrMalformedReview } sectionContent := func(heading, next string) string { start := strings.Index(text, heading) + len(heading) end := len(text) if next != "" { if index := strings.Index(text[start:], next); index >= 0 { end = start + index } } return strings.TrimSpace(text[start:end]) } if sectionContent("## Worker Item Status", "\n## Worker Changes") == "" || sectionContent("## Worker Changes", "\n## Worker Verification") == "" || sectionContent("## Worker Verification", "\n## Deviations") == "" || sectionContent("## Deviations", "") == "" { return ErrMalformedReview } statusHeading := "## Worker Item Status" statusIdx := strings.Index(text, statusHeading) if statusIdx < 0 { return ErrMalformedReview } statusSection := sectionContent(statusHeading, "\n## Worker Changes") // Exact line inventory: every line in the status section must correspond // to one plan ID in order, with the grammar "- Pn: completed". Blank lines, // prose, malformed bullets, and out-of-order or duplicate entries are all // rejected because the line count and each line's content are compared // directly against the plan ID inventory. lines := strings.Split(statusSection, "\n") if len(lines) != len(planIDs) { return ErrMalformedReview } for i, line := range lines { trimmed := strings.TrimSpace(line) if trimmed == "" { return ErrMalformedReview } expected := fmt.Sprintf("- %s: completed", planIDs[i]) if trimmed != expected { return ErrMalformedReview } } return nil }