Correct prompt definition selection and decoding

This commit is contained in:
2026-08-11 22:11:39 +00:00
parent a718762da1
commit 25f1ba0b30
4 changed files with 435 additions and 129 deletions

View File

@@ -5,10 +5,9 @@ import (
"context"
"errors"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
"gitea.maximumdirect.net/eric/promptkit/internal/domain"
@@ -116,33 +115,24 @@ func (r *filesystemRepository) GetPromptDefinition(ctx context.Context, id strin
}
relPath := filecatalog.RelativePath(r.dir, fullPath)
fileMatch := filecatalog.Stem(filepath.Base(fullPath)) == id
raw, err := loadPromptDefinitionFile(fullPath)
data, err := os.ReadFile(fullPath)
if err != nil {
if fileMatch || promptDefinitionFileHasID(fullPath, id) {
continue
}
raw, err := decodePromptDefinition(data)
if err != nil {
if promptDefinitionDataMatches(data, id, version) {
return nil, fmt.Errorf("%w: %s: %v", ErrInvalidYAML, relPath, err)
}
continue
}
def, err := normalizePromptDefinition(raw, r.sourceRoot, fullPath)
if err != nil {
if fileMatch || strings.TrimSpace(raw.ID) == id {
return nil, fmt.Errorf("%w: %s: %v", ErrInvalidPromptDefinition, relPath, err)
}
continue
}
if def.ID != id {
continue
}
if version != "" && def.Version != version {
if !promptDefinitionMatches(raw, id, version) {
continue
}
matches = append(matches, promptDefinitionMatch{
def: def,
path: relPath,
raw: raw,
sourcePath: fullPath,
path: relPath,
})
}
@@ -158,7 +148,12 @@ func (r *filesystemRepository) GetPromptDefinition(ctx context.Context, id strin
}
if len(matches) == 1 {
return matches[0].def, nil
match := matches[0]
def, err := normalizePromptDefinition(match.raw, r.sourceRoot, match.sourcePath)
if err != nil {
return nil, fmt.Errorf("%w: %s: %v", ErrInvalidPromptDefinition, match.path, err)
}
return def, nil
}
return nil, ErrPromptDefinitionNotFound
@@ -169,37 +164,9 @@ func (r *fsRepository) GetPromptDefinition(ctx context.Context, id string, versi
}
type promptDefinitionMatch struct {
def *domain.PromptDefinition
path string
}
func loadPromptDefinitionFile(path string) (*promptDefinitionFile, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read prompt definition file: %w", err)
}
var raw promptDefinitionFile
decoder := yaml.NewDecoder(bytes.NewReader(data))
decoder.KnownFields(true)
if err := decoder.Decode(&raw); err != nil {
return nil, err
}
return &raw, nil
}
func promptDefinitionFileHasID(path string, id string) bool {
data, err := os.ReadFile(path)
if err != nil {
return false
}
var raw struct {
ID string `yaml:"id"`
}
if err := yaml.NewDecoder(bytes.NewReader(data)).Decode(&raw); err != nil {
return false
}
return strings.TrimSpace(raw.ID) == id
raw *promptDefinitionFile
sourcePath string
path string
}
func loadPromptDefinition(ctx context.Context, fsys fs.FS, root string, sourceRoot contentSourceRoot, id string, version string) (*domain.PromptDefinition, error) {
@@ -223,40 +190,25 @@ func loadPromptDefinition(ctx context.Context, fsys fs.FS, root string, sourceRo
}
relPath := filecatalog.DisplayPath(root, fullPath)
fileMatch := filecatalog.Stem(path.Base(fullPath)) == id
data, err := fs.ReadFile(fsys, fullPath)
if err != nil {
if fileMatch {
return nil, fmt.Errorf("%w: %s: failed to read prompt definition file: %v", ErrInvalidYAML, relPath, err)
}
continue
}
raw, err := decodePromptDefinition(data)
if err != nil {
if fileMatch || promptDefinitionDataHasID(data, id) {
if promptDefinitionDataMatches(data, id, version) {
return nil, fmt.Errorf("%w: %s: %v", ErrInvalidYAML, relPath, err)
}
continue
}
def, err := normalizePromptDefinition(raw, sourceRoot, fullPath)
if err != nil {
if fileMatch || strings.TrimSpace(raw.ID) == id {
return nil, fmt.Errorf("%w: %s: %v", ErrInvalidPromptDefinition, relPath, err)
}
continue
}
if def.ID != id {
continue
}
if version != "" && def.Version != version {
if !promptDefinitionMatches(raw, id, version) {
continue
}
matches = append(matches, promptDefinitionMatch{
def: def,
path: relPath,
raw: raw,
sourcePath: fullPath,
path: relPath,
})
}
@@ -272,7 +224,12 @@ func loadPromptDefinition(ctx context.Context, fsys fs.FS, root string, sourceRo
}
if len(matches) == 1 {
return matches[0].def, nil
match := matches[0]
def, err := normalizePromptDefinition(match.raw, sourceRoot, match.sourcePath)
if err != nil {
return nil, fmt.Errorf("%w: %s: %v", ErrInvalidPromptDefinition, match.path, err)
}
return def, nil
}
return nil, ErrPromptDefinitionNotFound
@@ -285,17 +242,39 @@ func decodePromptDefinition(data []byte) (*promptDefinitionFile, error) {
if err := decoder.Decode(&raw); err != nil {
return nil, err
}
var additional yaml.Node
if err := decoder.Decode(&additional); err != io.EOF {
if err != nil {
return nil, err
}
return nil, errors.New("prompt definition file must contain exactly one YAML document")
}
return &raw, nil
}
func promptDefinitionDataHasID(data []byte, id string) bool {
func promptDefinitionDataMatches(data []byte, id string, version string) bool {
var raw struct {
ID string `yaml:"id"`
ID string `yaml:"id"`
Version string `yaml:"version"`
}
if err := yaml.NewDecoder(bytes.NewReader(data)).Decode(&raw); err != nil {
return false
}
return strings.TrimSpace(raw.ID) == id
return promptSelectorMatches(raw.ID, raw.Version, id, version)
}
func promptDefinitionMatches(raw *promptDefinitionFile, id string, version string) bool {
if raw == nil {
return false
}
return promptSelectorMatches(raw.ID, raw.Version, id, version)
}
func promptSelectorMatches(rawID string, rawVersion string, id string, version string) bool {
if strings.TrimSpace(rawID) != id {
return false
}
return version == "" || strings.TrimSpace(rawVersion) == version
}
func normalizePromptDefinition(raw *promptDefinitionFile, sourceRoot contentSourceRoot, sourcePath string) (*domain.PromptDefinition, error) {