From 433deb1c30e03c834f98ab60fcb145b1616736ac Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 17 Mar 2026 22:26:46 +0000 Subject: [PATCH] refactor: split templates/ into prompts/, tasks/, flows/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three distinct concepts in lib/: prompts/ — System prompts (PROMPT.md, HOW to work) tasks/ — Structured task plans (PLAN.md, WHAT to do) flows/ — Multi-phase workflows (orchestration) personas/ — Domain/role system prompts (WHO you are) API updated: prompts.Prompt("coding") — system prompt prompts.Task("bug-fix") — task plan prompts.Flow("prod-push-polish") — workflow prompts.Template() — backwards compat (searches both) templates/ dir reserved for future output templates (CodeRabbit report formatting, CLI output parsing, etc.) Co-Authored-By: Virgil --- .../{templates => flows}/prod-push-polish.md | 0 .../lib/{templates => prompts}/coding.md | 0 .../lib/{templates => prompts}/conventions.md | 0 .../lib/{templates => prompts}/default.md | 0 .../lib/{templates => prompts}/security.md | 0 .../lib/{templates => prompts}/verify.md | 0 .../{templates => tasks}/api-consistency.yaml | 0 .../lib/{templates => tasks}/bug-fix.yaml | 0 .../lib/{templates => tasks}/code-review.yaml | 0 .../lib/{templates => tasks}/dead-code.yaml | 0 .../dependency-audit.yaml | 0 .../lib/{templates => tasks}/doc-sync.yaml | 0 .../{templates => tasks}/feature-port.yaml | 0 .../lib/{templates => tasks}/new-feature.yaml | 0 .../lib/{templates => tasks}/refactor.yaml | 0 .../lib/{templates => tasks}/test-gaps.yaml | 0 pkg/prompts/prompts.go | 127 ++++++++++++------ pkg/prompts/prompts_test.go | 50 ++++++- 18 files changed, 130 insertions(+), 47 deletions(-) rename pkg/prompts/lib/{templates => flows}/prod-push-polish.md (100%) rename pkg/prompts/lib/{templates => prompts}/coding.md (100%) rename pkg/prompts/lib/{templates => prompts}/conventions.md (100%) rename pkg/prompts/lib/{templates => prompts}/default.md (100%) rename pkg/prompts/lib/{templates => prompts}/security.md (100%) rename pkg/prompts/lib/{templates => prompts}/verify.md (100%) rename pkg/prompts/lib/{templates => tasks}/api-consistency.yaml (100%) rename pkg/prompts/lib/{templates => tasks}/bug-fix.yaml (100%) rename pkg/prompts/lib/{templates => tasks}/code-review.yaml (100%) rename pkg/prompts/lib/{templates => tasks}/dead-code.yaml (100%) rename pkg/prompts/lib/{templates => tasks}/dependency-audit.yaml (100%) rename pkg/prompts/lib/{templates => tasks}/doc-sync.yaml (100%) rename pkg/prompts/lib/{templates => tasks}/feature-port.yaml (100%) rename pkg/prompts/lib/{templates => tasks}/new-feature.yaml (100%) rename pkg/prompts/lib/{templates => tasks}/refactor.yaml (100%) rename pkg/prompts/lib/{templates => tasks}/test-gaps.yaml (100%) diff --git a/pkg/prompts/lib/templates/prod-push-polish.md b/pkg/prompts/lib/flows/prod-push-polish.md similarity index 100% rename from pkg/prompts/lib/templates/prod-push-polish.md rename to pkg/prompts/lib/flows/prod-push-polish.md diff --git a/pkg/prompts/lib/templates/coding.md b/pkg/prompts/lib/prompts/coding.md similarity index 100% rename from pkg/prompts/lib/templates/coding.md rename to pkg/prompts/lib/prompts/coding.md diff --git a/pkg/prompts/lib/templates/conventions.md b/pkg/prompts/lib/prompts/conventions.md similarity index 100% rename from pkg/prompts/lib/templates/conventions.md rename to pkg/prompts/lib/prompts/conventions.md diff --git a/pkg/prompts/lib/templates/default.md b/pkg/prompts/lib/prompts/default.md similarity index 100% rename from pkg/prompts/lib/templates/default.md rename to pkg/prompts/lib/prompts/default.md diff --git a/pkg/prompts/lib/templates/security.md b/pkg/prompts/lib/prompts/security.md similarity index 100% rename from pkg/prompts/lib/templates/security.md rename to pkg/prompts/lib/prompts/security.md diff --git a/pkg/prompts/lib/templates/verify.md b/pkg/prompts/lib/prompts/verify.md similarity index 100% rename from pkg/prompts/lib/templates/verify.md rename to pkg/prompts/lib/prompts/verify.md diff --git a/pkg/prompts/lib/templates/api-consistency.yaml b/pkg/prompts/lib/tasks/api-consistency.yaml similarity index 100% rename from pkg/prompts/lib/templates/api-consistency.yaml rename to pkg/prompts/lib/tasks/api-consistency.yaml diff --git a/pkg/prompts/lib/templates/bug-fix.yaml b/pkg/prompts/lib/tasks/bug-fix.yaml similarity index 100% rename from pkg/prompts/lib/templates/bug-fix.yaml rename to pkg/prompts/lib/tasks/bug-fix.yaml diff --git a/pkg/prompts/lib/templates/code-review.yaml b/pkg/prompts/lib/tasks/code-review.yaml similarity index 100% rename from pkg/prompts/lib/templates/code-review.yaml rename to pkg/prompts/lib/tasks/code-review.yaml diff --git a/pkg/prompts/lib/templates/dead-code.yaml b/pkg/prompts/lib/tasks/dead-code.yaml similarity index 100% rename from pkg/prompts/lib/templates/dead-code.yaml rename to pkg/prompts/lib/tasks/dead-code.yaml diff --git a/pkg/prompts/lib/templates/dependency-audit.yaml b/pkg/prompts/lib/tasks/dependency-audit.yaml similarity index 100% rename from pkg/prompts/lib/templates/dependency-audit.yaml rename to pkg/prompts/lib/tasks/dependency-audit.yaml diff --git a/pkg/prompts/lib/templates/doc-sync.yaml b/pkg/prompts/lib/tasks/doc-sync.yaml similarity index 100% rename from pkg/prompts/lib/templates/doc-sync.yaml rename to pkg/prompts/lib/tasks/doc-sync.yaml diff --git a/pkg/prompts/lib/templates/feature-port.yaml b/pkg/prompts/lib/tasks/feature-port.yaml similarity index 100% rename from pkg/prompts/lib/templates/feature-port.yaml rename to pkg/prompts/lib/tasks/feature-port.yaml diff --git a/pkg/prompts/lib/templates/new-feature.yaml b/pkg/prompts/lib/tasks/new-feature.yaml similarity index 100% rename from pkg/prompts/lib/templates/new-feature.yaml rename to pkg/prompts/lib/tasks/new-feature.yaml diff --git a/pkg/prompts/lib/templates/refactor.yaml b/pkg/prompts/lib/tasks/refactor.yaml similarity index 100% rename from pkg/prompts/lib/templates/refactor.yaml rename to pkg/prompts/lib/tasks/refactor.yaml diff --git a/pkg/prompts/lib/templates/test-gaps.yaml b/pkg/prompts/lib/tasks/test-gaps.yaml similarity index 100% rename from pkg/prompts/lib/templates/test-gaps.yaml rename to pkg/prompts/lib/tasks/test-gaps.yaml diff --git a/pkg/prompts/prompts.go b/pkg/prompts/prompts.go index 3403adc..d1a07e1 100644 --- a/pkg/prompts/prompts.go +++ b/pkg/prompts/prompts.go @@ -1,13 +1,20 @@ // SPDX-License-Identifier: EUPL-1.2 -// Package prompts provides embedded prompt templates and personas for agent dispatch. -// Templates and personas are loaded from lib/ at compile time via go:embed. +// Package prompts provides embedded prompt content for agent dispatch. +// All content is loaded from lib/ at compile time via go:embed. +// +// Structure: +// +// lib/prompts/ — System prompts (PROMPT.md content, HOW to work) +// lib/tasks/ — Structured task plans (PLAN.md, WHAT to do) +// lib/flows/ — Multi-phase workflows (orchestration sequences) +// lib/personas/ — Domain/role system prompts (WHO you are) // // Usage: // -// template, _ := prompts.Template("bug-fix") -// persona, _ := prompts.Persona("engineering/engineering-security-engineer") -// all := prompts.ListTemplates() +// prompt, _ := prompts.Prompt("coding") +// task, _ := prompts.Task("bug-fix") +// persona, _ := prompts.Persona("secops/developer") package prompts import ( @@ -17,18 +24,42 @@ import ( "strings" ) -//go:embed lib/templates/*.yaml lib/templates/*.md -var templateFS embed.FS +//go:embed lib/prompts/*.md +var promptFS embed.FS + +//go:embed lib/tasks/*.yaml +var taskFS embed.FS + +//go:embed lib/flows/*.md +var flowFS embed.FS //go:embed lib/personas var personaFS embed.FS -// Template returns the content of a prompt template by slug. -// Slug examples: "bug-fix", "code-review", "security". +// Prompt returns a system prompt by slug (written as PROMPT.md). +// Slugs: "coding", "verify", "conventions", "security", "default". +func Prompt(slug string) (string, error) { + data, err := promptFS.ReadFile("lib/prompts/" + slug + ".md") + if err != nil { + return "", err + } + return string(data), nil +} + +// Template is an alias for Prompt (backwards compatibility). func Template(slug string) (string, error) { - // Try .yaml first, then .yml, then .md - for _, ext := range []string{".yaml", ".yml", ".md"} { - data, err := templateFS.ReadFile("lib/templates/" + slug + ext) + // Try prompts first, then tasks + if content, err := Prompt(slug); err == nil { + return content, nil + } + return Task(slug) +} + +// Task returns a structured task plan by slug (written as PLAN.md). +// Slugs: "bug-fix", "new-feature", "refactor", "code-review", etc. +func Task(slug string) (string, error) { + for _, ext := range []string{".yaml", ".yml"} { + data, err := taskFS.ReadFile("lib/tasks/" + slug + ext) if err == nil { return string(data), nil } @@ -36,9 +67,17 @@ func Template(slug string) (string, error) { return "", fs.ErrNotExist } -// Persona returns the content of a persona by path. -// Path examples: "engineering/engineering-security-engineer", -// "testing/testing-api-tester", "specialized/blockchain-security-auditor". +// Flow returns a multi-phase workflow by slug. +func Flow(slug string) (string, error) { + data, err := flowFS.ReadFile("lib/flows/" + slug + ".md") + if err != nil { + return "", err + } + return string(data), nil +} + +// Persona returns a domain/role system prompt by path. +// Paths: "secops/developer", "code/backend-architect", "smm/tiktok-strategist". func Persona(path string) (string, error) { data, err := personaFS.ReadFile("lib/personas/" + path + ".md") if err != nil { @@ -47,23 +86,24 @@ func Persona(path string) (string, error) { return string(data), nil } -// ListTemplates returns all available template slugs. +// ListPrompts returns all available prompt slugs. +func ListPrompts() []string { + return listDir(promptFS, "lib/prompts") +} + +// ListTasks returns all available task plan slugs. +func ListTasks() []string { + return listDir(taskFS, "lib/tasks") +} + +// ListFlows returns all available flow slugs. +func ListFlows() []string { + return listDir(flowFS, "lib/flows") +} + +// ListTemplates returns all prompt + task slugs (backwards compatibility). func ListTemplates() []string { - entries, err := templateFS.ReadDir("lib/templates") - if err != nil { - return nil - } - var slugs []string - for _, e := range entries { - if e.IsDir() { - continue - } - name := e.Name() - ext := filepath.Ext(name) - slug := strings.TrimSuffix(name, ext) - slugs = append(slugs, slug) - } - return slugs + return append(ListPrompts(), ListTasks()...) } // ListPersonas returns all available persona paths. @@ -74,7 +114,6 @@ func ListPersonas() []string { return nil } if strings.HasSuffix(path, ".md") { - // Strip prefix and extension: lib/personas/engineering/foo.md → engineering/foo rel := strings.TrimPrefix(path, "lib/personas/") rel = strings.TrimSuffix(rel, ".md") paths = append(paths, rel) @@ -84,12 +123,20 @@ func ListPersonas() []string { return paths } -// TemplateFS returns the raw embedded filesystem for templates. -func TemplateFS() embed.FS { - return templateFS -} - -// PersonaFS returns the raw embedded filesystem for personas. -func PersonaFS() embed.FS { - return personaFS +// listDir returns slugs (filename without extension) from an embedded directory. +func listDir(fsys embed.FS, dir string) []string { + entries, err := fsys.ReadDir(dir) + if err != nil { + return nil + } + var slugs []string + for _, e := range entries { + if e.IsDir() { + continue + } + name := e.Name() + ext := filepath.Ext(name) + slugs = append(slugs, strings.TrimSuffix(name, ext)) + } + return slugs } diff --git a/pkg/prompts/prompts_test.go b/pkg/prompts/prompts_test.go index b9c1efb..145b357 100644 --- a/pkg/prompts/prompts_test.go +++ b/pkg/prompts/prompts_test.go @@ -10,21 +10,57 @@ import ( "github.com/stretchr/testify/require" ) -func TestTemplate_Good_YAML(t *testing.T) { - content, err := Template("bug-fix") +func TestPrompt_Good(t *testing.T) { + content, err := Prompt("coding") + require.NoError(t, err) + assert.Contains(t, content, "SANDBOX") + assert.Contains(t, content, "Closeout Sequence") +} + +func TestPrompt_Bad_NotFound(t *testing.T) { + _, err := Prompt("nonexistent") + assert.Error(t, err) +} + +func TestTask_Good(t *testing.T) { + content, err := Task("bug-fix") require.NoError(t, err) assert.Contains(t, content, "name:") } -func TestTemplate_Good_MD(t *testing.T) { - content, err := Template("prod-push-polish") +func TestTask_Bad_NotFound(t *testing.T) { + _, err := Task("nonexistent") + assert.Error(t, err) +} + +func TestTemplate_Good_BackwardsCompat(t *testing.T) { + // Template() should find prompts + content, err := Template("coding") + require.NoError(t, err) + assert.Contains(t, content, "SANDBOX") + + // Template() should also find tasks + content, err = Template("bug-fix") + require.NoError(t, err) + assert.Contains(t, content, "name:") +} + +func TestFlow_Good(t *testing.T) { + content, err := Flow("prod-push-polish") require.NoError(t, err) assert.True(t, len(content) > 0) } -func TestTemplate_Bad_NotFound(t *testing.T) { - _, err := Template("nonexistent-template") - assert.Error(t, err) +func TestListPrompts_Good(t *testing.T) { + list := ListPrompts() + assert.Contains(t, list, "coding") + assert.Contains(t, list, "verify") +} + +func TestListTasks_Good(t *testing.T) { + list := ListTasks() + assert.Contains(t, list, "bug-fix") + assert.Contains(t, list, "refactor") } func TestPersona_Good(t *testing.T) {