diff --git a/internal/cli/catalog.go b/internal/cli/catalog.go index 8cd11de..5228d5a 100644 --- a/internal/cli/catalog.go +++ b/internal/cli/catalog.go @@ -16,7 +16,6 @@ import ( "gitea.maximumdirect.net/eric/notarius/internal/modules/merge/appendorder" "gitea.maximumdirect.net/eric/notarius/internal/modules/normalize/noop" jsonoutput "gitea.maximumdirect.net/eric/notarius/internal/modules/output/json" - "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets" ) func productionRegistries() (pipeline.Registries, error) { @@ -63,9 +62,6 @@ func productionCatalog() (pipeline.ModuleCatalog, error) { func productionPromptAssets() (*llm.AssetRegistry, error) { registry := llm.NewAssetRegistry() - if err := sharedassets.Register(registry); err != nil { - return nil, fmt.Errorf("register shared dnd prompt assets: %w", err) - } if err := scenes.RegisterPromptAssets(registry); err != nil { return nil, fmt.Errorf("register dnd scenes prompt assets: %w", err) } diff --git a/internal/cli/run_test.go b/internal/cli/run_test.go index 8f66f61..8a76b1a 100644 --- a/internal/cli/run_test.go +++ b/internal/cli/run_test.go @@ -191,9 +191,6 @@ func TestProductionPromptAssetsRegisterAndPrepareDndPrompts(t *testing.T) { t.Fatalf("PromptFS() error = %v, want nil", err) } for _, name := range []string{ - "common-dnd-system.md", - "common-dnd-transcript.md", - "common-dnd-references.md", "dnd.scenes/dnd.scenes.yaml", "dnd.scenes/task.md", "dnd.scenes/instructions.md", diff --git a/internal/modules/chunk/dnd/scenes/scriptorium_assets.go b/internal/modules/chunk/dnd/scenes/scriptorium_assets.go index 284cde9..070ce17 100644 --- a/internal/modules/chunk/dnd/scenes/scriptorium_assets.go +++ b/internal/modules/chunk/dnd/scenes/scriptorium_assets.go @@ -9,16 +9,17 @@ import ( "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets" + "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd" ) const scriptoriumPromptRoot = "assets/prompts" func RegisterPromptAssets(registry *llm.AssetRegistry) error { - promptFS, err := sharedassets.ModulePromptFS("dnd.scenes", embeddedAssets, []sharedassets.ModulePromptFile{ + promptFS, err := dnd.ModulePromptFS("dnd.scenes", embeddedAssets, []sharedassets.ModulePromptFile{ {Name: "dnd.scenes.yaml", Path: "assets/prompts/dnd.scenes.yaml"}, {Name: "task.md", Path: "assets/prompts/task.md"}, {Name: "instructions.md", Path: "assets/prompts/instructions.md"}, - }, sharedassets.SharedPromptFiles()...) + }) if err != nil { return fmt.Errorf("prepare scene prompt assets: %w", err) } @@ -64,7 +65,7 @@ func scriptoriumPromptMetadata() (string, error) { {FS: embeddedAssets, Path: "assets/prompts/dnd.scenes.yaml"}, {FS: embeddedAssets, Path: "assets/prompts/task.md"}, {FS: embeddedAssets, Path: "assets/prompts/instructions.md"}, - }, append(sharedassets.CommonHashParts(), sharedassets.ReferenceHashParts()...)...) + }, append(dnd.CommonHashParts(), dnd.ReferenceHashParts()...)...) scriptoriumPromptHash, scriptoriumPromptHashErr = llm.HashAssets(parts) }) return scriptoriumPromptHash, scriptoriumPromptHashErr diff --git a/internal/modules/chunk/dnd/scenes/scriptorium_assets_test.go b/internal/modules/chunk/dnd/scenes/scriptorium_assets_test.go index a39cc25..e480bf2 100644 --- a/internal/modules/chunk/dnd/scenes/scriptorium_assets_test.go +++ b/internal/modules/chunk/dnd/scenes/scriptorium_assets_test.go @@ -8,7 +8,6 @@ import ( "time" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" - "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets" "gitea.maximumdirect.net/eric/scriptorium" ) @@ -90,9 +89,6 @@ func TestScriptoriumPromptDiagnosticsOmitRawMaterials(t *testing.T) { func prepareScenesPrompt(t *testing.T, transcript []byte, players string, party string, glossary string) *scriptorium.PreparedRun { t.Helper() registry := llm.NewAssetRegistry() - if err := sharedassets.Register(registry); err != nil { - t.Fatalf("register shared prompt assets: %v", err) - } if err := RegisterPromptAssets(registry); err != nil { t.Fatalf("register scene prompt assets: %v", err) } diff --git a/internal/modules/extract/dnd/spells/scriptorium_assets.go b/internal/modules/extract/dnd/spells/scriptorium_assets.go index f661f89..ce497f4 100644 --- a/internal/modules/extract/dnd/spells/scriptorium_assets.go +++ b/internal/modules/extract/dnd/spells/scriptorium_assets.go @@ -9,16 +9,17 @@ import ( "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets" + "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets/dnd" ) const scriptoriumPromptRoot = "assets/prompts" func RegisterPromptAssets(registry *llm.AssetRegistry) error { - promptFS, err := sharedassets.ModulePromptFS("dnd.spells", embeddedAssets, []sharedassets.ModulePromptFile{ + promptFS, err := dnd.ModulePromptFS("dnd.spells", embeddedAssets, []sharedassets.ModulePromptFile{ {Name: "dnd.spells.yaml", Path: "assets/prompts/dnd.spells.yaml"}, {Name: "task.md", Path: "assets/prompts/task.md"}, {Name: "instructions.md", Path: "assets/prompts/instructions.md"}, - }, sharedassets.SharedPromptFiles()...) + }) if err != nil { return fmt.Errorf("prepare spell prompt assets: %w", err) } @@ -64,7 +65,7 @@ func scriptoriumPromptMetadata() (string, error) { {FS: embeddedAssets, Path: "assets/prompts/dnd.spells.yaml"}, {FS: embeddedAssets, Path: "assets/prompts/task.md"}, {FS: embeddedAssets, Path: "assets/prompts/instructions.md"}, - }, append(sharedassets.CommonHashParts(), sharedassets.ReferenceHashParts()...)...) + }, append(dnd.CommonHashParts(), dnd.ReferenceHashParts()...)...) scriptoriumPromptHash, scriptoriumPromptHashErr = llm.HashAssets(parts) }) return scriptoriumPromptHash, scriptoriumPromptHashErr diff --git a/internal/modules/extract/dnd/spells/scriptorium_assets_test.go b/internal/modules/extract/dnd/spells/scriptorium_assets_test.go index fac4fb7..407bce8 100644 --- a/internal/modules/extract/dnd/spells/scriptorium_assets_test.go +++ b/internal/modules/extract/dnd/spells/scriptorium_assets_test.go @@ -9,7 +9,6 @@ import ( "gitea.maximumdirect.net/eric/notarius/internal/framework/contracts" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" - "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets" "gitea.maximumdirect.net/eric/scriptorium" ) @@ -140,9 +139,6 @@ func TestScriptoriumPromptDiagnosticsOmitRawMaterials(t *testing.T) { func prepareSpellsPrompt(t *testing.T, transcript []byte, players string, party string, glossary string) *scriptorium.PreparedRun { t.Helper() registry := llm.NewAssetRegistry() - if err := sharedassets.Register(registry); err != nil { - t.Fatalf("register shared prompt assets: %v", err) - } if err := RegisterPromptAssets(registry); err != nil { t.Fatalf("register spell prompt assets: %v", err) } diff --git a/internal/modules/sharedassets/assets.go b/internal/modules/sharedassets/dnd/assets.go similarity index 56% rename from internal/modules/sharedassets/assets.go rename to internal/modules/sharedassets/dnd/assets.go index be3a323..8293376 100644 --- a/internal/modules/sharedassets/assets.go +++ b/internal/modules/sharedassets/dnd/assets.go @@ -1,28 +1,26 @@ -package sharedassets +package dnd import ( "embed" + "io/fs" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" + "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets" ) //go:embed assets/prompts/*.md var embeddedAssets embed.FS -var commonDNDPromptFiles = []string{ +var sharedPromptFiles = []string{ "common-dnd-system.md", "common-dnd-transcript.md", "common-dnd-references.md", } -func Register(registry *llm.AssetRegistry) error { - return registry.RegisterPromptFS(embeddedAssets, "assets/prompts") -} - -func SharedPromptFiles() []SharedPromptFile { - files := make([]SharedPromptFile, 0, len(commonDNDPromptFiles)) - for _, name := range commonDNDPromptFiles { - files = append(files, SharedPromptFile{ +func SharedPromptFiles() []sharedassets.SharedPromptFile { + files := make([]sharedassets.SharedPromptFile, 0, len(sharedPromptFiles)) + for _, name := range sharedPromptFiles { + files = append(files, sharedassets.SharedPromptFile{ Name: name, FS: embeddedAssets, Path: "assets/prompts/" + name, @@ -43,3 +41,7 @@ func ReferenceHashParts() []llm.AssetHashPart { {FS: embeddedAssets, Path: "assets/prompts/common-dnd-references.md"}, } } + +func ModulePromptFS(moduleDir string, moduleFS fs.FS, files []sharedassets.ModulePromptFile) (fs.FS, error) { + return sharedassets.ModulePromptFS(moduleDir, moduleFS, files, SharedPromptFiles()...) +} diff --git a/internal/modules/sharedassets/assets/prompts/common-dnd-references.md b/internal/modules/sharedassets/dnd/assets/prompts/common-dnd-references.md similarity index 100% rename from internal/modules/sharedassets/assets/prompts/common-dnd-references.md rename to internal/modules/sharedassets/dnd/assets/prompts/common-dnd-references.md diff --git a/internal/modules/sharedassets/assets/prompts/common-dnd-system.md b/internal/modules/sharedassets/dnd/assets/prompts/common-dnd-system.md similarity index 100% rename from internal/modules/sharedassets/assets/prompts/common-dnd-system.md rename to internal/modules/sharedassets/dnd/assets/prompts/common-dnd-system.md diff --git a/internal/modules/sharedassets/assets/prompts/common-dnd-transcript.md b/internal/modules/sharedassets/dnd/assets/prompts/common-dnd-transcript.md similarity index 100% rename from internal/modules/sharedassets/assets/prompts/common-dnd-transcript.md rename to internal/modules/sharedassets/dnd/assets/prompts/common-dnd-transcript.md diff --git a/internal/modules/sharedassets/dnd/assets_test.go b/internal/modules/sharedassets/dnd/assets_test.go new file mode 100644 index 0000000..686ce6f --- /dev/null +++ b/internal/modules/sharedassets/dnd/assets_test.go @@ -0,0 +1,87 @@ +package dnd + +import ( + "io/fs" + "testing" + "testing/fstest" + + "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" + "gitea.maximumdirect.net/eric/notarius/internal/modules/sharedassets" +) + +func TestSharedPromptFilesReturnsNewSlice(t *testing.T) { + first := SharedPromptFiles() + second := SharedPromptFiles() + + if len(first) != 3 || len(second) != 3 { + t.Fatalf("SharedPromptFiles() lengths = %d and %d, want 3", len(first), len(second)) + } + first[0].Name = "changed.md" + if second[0].Name != "common-dnd-system.md" { + t.Fatalf("SharedPromptFiles() reused descriptor slice: %#v", second) + } +} + +func TestSharedPromptFilesReferenceEmbeddedAssets(t *testing.T) { + for _, file := range SharedPromptFiles() { + if file.FS == nil { + t.Fatalf("SharedPromptFiles() descriptor %q has nil FS", file.Name) + } + if _, err := fs.ReadFile(file.FS, file.Path); err != nil { + t.Fatalf("ReadFile(%q) error = %v, want nil", file.Path, err) + } + } +} + +func TestHashPartsReferenceSharedPrompts(t *testing.T) { + assertHashParts(t, "common", CommonHashParts(), []string{ + "assets/prompts/common-dnd-system.md", + "assets/prompts/common-dnd-transcript.md", + }) + assertHashParts(t, "reference", ReferenceHashParts(), []string{ + "assets/prompts/common-dnd-references.md", + }) + + for _, part := range append(CommonHashParts(), ReferenceHashParts()...) { + if _, err := fs.ReadFile(part.FS, part.Path); err != nil { + t.Fatalf("ReadFile(%q) error = %v, want nil", part.Path, err) + } + } +} + +func assertHashParts(t *testing.T, name string, parts []llm.AssetHashPart, want []string) { + t.Helper() + if len(parts) != len(want) { + t.Fatalf("%s hash parts length = %d, want %d", name, len(parts), len(want)) + } + for i, part := range parts { + if part.Path != want[i] { + t.Fatalf("%s hash part %d path = %q, want %q", name, i, part.Path, want[i]) + } + if part.FS == nil { + t.Fatalf("%s hash part %d has nil FS", name, i) + } + } +} + +func TestModulePromptFSMountsDNDSharedPrompts(t *testing.T) { + fsys, err := ModulePromptFS("dnd.test", fstest.MapFS{ + "assets/prompts/dnd.test.yaml": {Data: []byte("id: dnd.test")}, + }, []sharedassets.ModulePromptFile{ + {Name: "dnd.test.yaml", Path: "assets/prompts/dnd.test.yaml"}, + }) + if err != nil { + t.Fatalf("ModulePromptFS() error = %v, want nil", err) + } + + for _, path := range []string{ + "assets/prompts/dnd.test/dnd.test.yaml", + "assets/prompts/dnd.test/sharedassets/common-dnd-system.md", + "assets/prompts/dnd.test/sharedassets/common-dnd-transcript.md", + "assets/prompts/dnd.test/sharedassets/common-dnd-references.md", + } { + if _, err := fs.ReadFile(fsys, path); err != nil { + t.Fatalf("ReadFile(%q) error = %v, want nil", path, err) + } + } +}