From 08954f17e2da024e6e37f68dfbdcd34989c3f8bd Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 5 Aug 2026 00:37:19 +0000 Subject: [PATCH] Centralize shared D&D LLM assets --- .../schemas/dnd_entity_reconcile_llm.v1.json | 0 .../dnd}/profiles/dnd-extraction.yaml | 0 .../common-dnd-entity-reconciliation.md | 0 .../prompts/common-dnd-extraction-evidence.md | 0 .../shared}/prompts/common-dnd-identity.md | 0 .../dnd/shared}/prompts/common-dnd-npcs.md | 0 .../shared}/prompts/common-dnd-references.md | 0 .../dnd/shared}/prompts/common-dnd-system.md | 0 .../shared}/prompts/common-dnd-transcript.md | 0 assets/package.go | 2 +- internal/modules/dnd/register/profiles.go | 13 ++++--- internal/modules/dnd/shared/assets.go | 34 ++++++++++++------- internal/modules/dnd/shared/assets_test.go | 18 ++++++---- .../dnd/shared/entityreconcile/assets.go | 6 ---- .../dnd/shared/entityreconcile/schema.go | 30 ++++++++++++++-- 15 files changed, 69 insertions(+), 34 deletions(-) rename {internal/modules/dnd/shared/entityreconcile/assets => assets/dnd/entity-reconciliation}/schemas/dnd_entity_reconcile_llm.v1.json (100%) rename {internal/modules/dnd/register/assets => assets/dnd}/profiles/dnd-extraction.yaml (100%) rename {internal/modules/dnd/shared/assets => assets/dnd/shared}/prompts/common-dnd-entity-reconciliation.md (100%) rename {internal/modules/dnd/shared/assets => assets/dnd/shared}/prompts/common-dnd-extraction-evidence.md (100%) rename {internal/modules/dnd/shared/assets => assets/dnd/shared}/prompts/common-dnd-identity.md (100%) rename {internal/modules/dnd/shared/assets => assets/dnd/shared}/prompts/common-dnd-npcs.md (100%) rename {internal/modules/dnd/shared/assets => assets/dnd/shared}/prompts/common-dnd-references.md (100%) rename {internal/modules/dnd/shared/assets => assets/dnd/shared}/prompts/common-dnd-system.md (100%) rename {internal/modules/dnd/shared/assets => assets/dnd/shared}/prompts/common-dnd-transcript.md (100%) delete mode 100644 internal/modules/dnd/shared/entityreconcile/assets.go diff --git a/internal/modules/dnd/shared/entityreconcile/assets/schemas/dnd_entity_reconcile_llm.v1.json b/assets/dnd/entity-reconciliation/schemas/dnd_entity_reconcile_llm.v1.json similarity index 100% rename from internal/modules/dnd/shared/entityreconcile/assets/schemas/dnd_entity_reconcile_llm.v1.json rename to assets/dnd/entity-reconciliation/schemas/dnd_entity_reconcile_llm.v1.json diff --git a/internal/modules/dnd/register/assets/profiles/dnd-extraction.yaml b/assets/dnd/profiles/dnd-extraction.yaml similarity index 100% rename from internal/modules/dnd/register/assets/profiles/dnd-extraction.yaml rename to assets/dnd/profiles/dnd-extraction.yaml diff --git a/internal/modules/dnd/shared/assets/prompts/common-dnd-entity-reconciliation.md b/assets/dnd/shared/prompts/common-dnd-entity-reconciliation.md similarity index 100% rename from internal/modules/dnd/shared/assets/prompts/common-dnd-entity-reconciliation.md rename to assets/dnd/shared/prompts/common-dnd-entity-reconciliation.md diff --git a/internal/modules/dnd/shared/assets/prompts/common-dnd-extraction-evidence.md b/assets/dnd/shared/prompts/common-dnd-extraction-evidence.md similarity index 100% rename from internal/modules/dnd/shared/assets/prompts/common-dnd-extraction-evidence.md rename to assets/dnd/shared/prompts/common-dnd-extraction-evidence.md diff --git a/internal/modules/dnd/shared/assets/prompts/common-dnd-identity.md b/assets/dnd/shared/prompts/common-dnd-identity.md similarity index 100% rename from internal/modules/dnd/shared/assets/prompts/common-dnd-identity.md rename to assets/dnd/shared/prompts/common-dnd-identity.md diff --git a/internal/modules/dnd/shared/assets/prompts/common-dnd-npcs.md b/assets/dnd/shared/prompts/common-dnd-npcs.md similarity index 100% rename from internal/modules/dnd/shared/assets/prompts/common-dnd-npcs.md rename to assets/dnd/shared/prompts/common-dnd-npcs.md diff --git a/internal/modules/dnd/shared/assets/prompts/common-dnd-references.md b/assets/dnd/shared/prompts/common-dnd-references.md similarity index 100% rename from internal/modules/dnd/shared/assets/prompts/common-dnd-references.md rename to assets/dnd/shared/prompts/common-dnd-references.md diff --git a/internal/modules/dnd/shared/assets/prompts/common-dnd-system.md b/assets/dnd/shared/prompts/common-dnd-system.md similarity index 100% rename from internal/modules/dnd/shared/assets/prompts/common-dnd-system.md rename to assets/dnd/shared/prompts/common-dnd-system.md diff --git a/internal/modules/dnd/shared/assets/prompts/common-dnd-transcript.md b/assets/dnd/shared/prompts/common-dnd-transcript.md similarity index 100% rename from internal/modules/dnd/shared/assets/prompts/common-dnd-transcript.md rename to assets/dnd/shared/prompts/common-dnd-transcript.md diff --git a/assets/package.go b/assets/package.go index 1a1fbdf..5f6838f 100644 --- a/assets/package.go +++ b/assets/package.go @@ -6,7 +6,7 @@ import ( "io/fs" ) -//go:embed generic +//go:embed dnd generic var embedded embed.FS // FS returns the embedded read-only asset filesystem. diff --git a/internal/modules/dnd/register/profiles.go b/internal/modules/dnd/register/profiles.go index 96186b2..ee29549 100644 --- a/internal/modules/dnd/register/profiles.go +++ b/internal/modules/dnd/register/profiles.go @@ -1,14 +1,17 @@ package register import ( - "embed" + "fmt" + "io/fs" + rootassets "gitea.maximumdirect.net/eric/notarius/assets" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" ) -//go:embed assets/profiles/*.yaml -var embeddedProfileAssets embed.FS - func registerFallbackProfiles(assets *llm.AssetRegistry) error { - return assets.RegisterFallbackProfileFS(embeddedProfileAssets, "assets/profiles") + profiles, err := fs.Sub(rootassets.FS(), "dnd/profiles") + if err != nil { + return fmt.Errorf("scope D&D fallback profiles: %w", err) + } + return assets.RegisterFallbackProfileFS(profiles, ".") } diff --git a/internal/modules/dnd/shared/assets.go b/internal/modules/dnd/shared/assets.go index cafc407..184f507 100644 --- a/internal/modules/dnd/shared/assets.go +++ b/internal/modules/dnd/shared/assets.go @@ -1,18 +1,15 @@ package shared import ( - "embed" "fmt" "io/fs" "strings" + rootassets "gitea.maximumdirect.net/eric/notarius/assets" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" "gitea.maximumdirect.net/eric/notarius/internal/framework/promptfs" ) -//go:embed assets/prompts/*.md -var embeddedAssets embed.FS - // PromptAssetManifest is the ordered set of assets that make up one prompt. // Module files are addressed in the owning module filesystem; shared files use // the names in sharedPromptPaths and are mounted beneath sharedassets. @@ -23,13 +20,21 @@ type PromptAssetManifest struct { } var sharedPromptPaths = map[string]string{ - "common-dnd-system.md": "assets/prompts/common-dnd-system.md", - "common-dnd-extraction-evidence.md": "assets/prompts/common-dnd-extraction-evidence.md", - "common-dnd-identity.md": "assets/prompts/common-dnd-identity.md", - "common-dnd-transcript.md": "assets/prompts/common-dnd-transcript.md", - "common-dnd-references.md": "assets/prompts/common-dnd-references.md", - "common-dnd-npcs.md": "assets/prompts/common-dnd-npcs.md", - "common-dnd-entity-reconciliation.md": "assets/prompts/common-dnd-entity-reconciliation.md", + "common-dnd-system.md": "prompts/common-dnd-system.md", + "common-dnd-extraction-evidence.md": "prompts/common-dnd-extraction-evidence.md", + "common-dnd-identity.md": "prompts/common-dnd-identity.md", + "common-dnd-transcript.md": "prompts/common-dnd-transcript.md", + "common-dnd-references.md": "prompts/common-dnd-references.md", + "common-dnd-npcs.md": "prompts/common-dnd-npcs.md", + "common-dnd-entity-reconciliation.md": "prompts/common-dnd-entity-reconciliation.md", +} + +func sharedAssetFS() (fs.FS, error) { + assets, err := fs.Sub(rootassets.FS(), "dnd/shared") + if err != nil { + return nil, fmt.Errorf("scope shared D&D prompt assets: %w", err) + } + return assets, nil } func (manifest PromptAssetManifest) PromptFS(moduleFS fs.FS) (fs.FS, error) { @@ -57,6 +62,11 @@ func (manifest PromptAssetManifest) Hash(moduleFS fs.FS) (string, error) { } func resolveSharedPromptFiles(names []string) ([]promptfs.SharedPromptFile, error) { + assets, err := sharedAssetFS() + if err != nil { + return nil, err + } + files := make([]promptfs.SharedPromptFile, 0, len(names)) seen := make(map[string]struct{}, len(names)) for _, name := range names { @@ -76,7 +86,7 @@ func resolveSharedPromptFiles(names []string) ([]promptfs.SharedPromptFile, erro seen[name] = struct{}{} files = append(files, promptfs.SharedPromptFile{ Name: name, - FS: embeddedAssets, + FS: assets, Path: path, }) } diff --git a/internal/modules/dnd/shared/assets_test.go b/internal/modules/dnd/shared/assets_test.go index 5dcfebc..b162c89 100644 --- a/internal/modules/dnd/shared/assets_test.go +++ b/internal/modules/dnd/shared/assets_test.go @@ -111,7 +111,7 @@ func TestPromptAssetManifestRejectsMissingModuleFile(t *testing.T) { func TestPromptAssetManifestRejectsMissingSharedFile(t *testing.T) { const name = "missing-for-test.md" - const path = "assets/prompts/missing-for-test.md" + const path = "prompts/missing-for-test.md" previous, existed := sharedPromptPaths[name] sharedPromptPaths[name] = path t.Cleanup(func() { @@ -157,11 +157,15 @@ func TestPromptAssetManifestHashMatchesManifestParts(t *testing.T) { if err != nil { t.Fatalf("Hash() error = %v, want nil", err) } + sharedFS, err := sharedAssetFS() + if err != nil { + t.Fatalf("sharedAssetFS() error = %v, want nil", err) + } want, err := llm.HashAssets([]llm.AssetHashPart{ {FS: moduleFS, Path: "assets/prompts/dnd.test.yaml"}, {FS: moduleFS, Path: "assets/prompts/task.md"}, - {FS: embeddedAssets, Path: "assets/prompts/common-dnd-transcript.md"}, - {FS: embeddedAssets, Path: "assets/prompts/common-dnd-system.md"}, + {FS: sharedFS, Path: "prompts/common-dnd-transcript.md"}, + {FS: sharedFS, Path: "prompts/common-dnd-system.md"}, }) if err != nil { t.Fatalf("HashAssets() error = %v, want nil", err) @@ -172,9 +176,9 @@ func TestPromptAssetManifestHashMatchesManifestParts(t *testing.T) { withUnused, err := llm.HashAssets([]llm.AssetHashPart{ {FS: moduleFS, Path: "assets/prompts/dnd.test.yaml"}, {FS: moduleFS, Path: "assets/prompts/task.md"}, - {FS: embeddedAssets, Path: "assets/prompts/common-dnd-transcript.md"}, - {FS: embeddedAssets, Path: "assets/prompts/common-dnd-system.md"}, - {FS: embeddedAssets, Path: "assets/prompts/common-dnd-npcs.md"}, + {FS: sharedFS, Path: "prompts/common-dnd-transcript.md"}, + {FS: sharedFS, Path: "prompts/common-dnd-system.md"}, + {FS: sharedFS, Path: "prompts/common-dnd-npcs.md"}, }) if err != nil { t.Fatalf("HashAssets() with unused asset error = %v, want nil", err) @@ -195,7 +199,7 @@ func TestSharedPromptDescriptorsReturnFreshCopies(t *testing.T) { } first[0].Name = "changed.md" first[0].Path = "changed.md" - if reflect.DeepEqual(first, second) || second[0].Name != "common-dnd-system.md" || second[0].Path != "assets/prompts/common-dnd-system.md" { + if reflect.DeepEqual(first, second) || second[0].Name != "common-dnd-system.md" || second[0].Path != "prompts/common-dnd-system.md" { t.Fatalf("resolveSharedPromptFiles() reused descriptor state: first=%#v second=%#v", first, second) } } diff --git a/internal/modules/dnd/shared/entityreconcile/assets.go b/internal/modules/dnd/shared/entityreconcile/assets.go deleted file mode 100644 index 90269cc..0000000 --- a/internal/modules/dnd/shared/entityreconcile/assets.go +++ /dev/null @@ -1,6 +0,0 @@ -package entityreconcile - -import "embed" - -//go:embed assets/schemas/dnd_entity_reconcile_llm.v1.json -var embeddedAssets embed.FS diff --git a/internal/modules/dnd/shared/entityreconcile/schema.go b/internal/modules/dnd/shared/entityreconcile/schema.go index d4dacd9..0039874 100644 --- a/internal/modules/dnd/shared/entityreconcile/schema.go +++ b/internal/modules/dnd/shared/entityreconcile/schema.go @@ -1,6 +1,10 @@ package entityreconcile import ( + "fmt" + "io/fs" + + rootassets "gitea.maximumdirect.net/eric/notarius/assets" "gitea.maximumdirect.net/eric/notarius/internal/framework/llm" ) @@ -9,13 +13,25 @@ const ( ResponseSchemaID = "notarius.dnd.entity_reconcile.llm" ResponseSchemaName = "notarius_dnd_entity_reconcile_llm_v1" SchemaVersion = "v1" - SchemaAssetPath = "assets/schemas/dnd_entity_reconcile_llm.v1.json" + SchemaAssetPath = "schemas/dnd_entity_reconcile_llm.v1.json" ) +func schemaAssetFS() (fs.FS, error) { + assets, err := fs.Sub(rootassets.FS(), "dnd/entity-reconciliation") + if err != nil { + return nil, fmt.Errorf("scope entity reconciliation assets: %w", err) + } + return assets, nil +} + // LoadResponseSchema returns the shared private duplicate-group response // contract. It is intentionally separate from durable artifact schemas. func LoadResponseSchema() (llm.ResponseSchema, error) { - return llm.LoadResponseSchema(embeddedAssets, llm.ResponseSchemaDefinition{ + assets, err := schemaAssetFS() + if err != nil { + return llm.ResponseSchema{}, err + } + return llm.LoadResponseSchema(assets, llm.ResponseSchemaDefinition{ Key: ResponseSchemaKey, ID: ResponseSchemaID, Version: SchemaVersion, @@ -27,5 +43,13 @@ func LoadResponseSchema() (llm.ResponseSchema, error) { // RegisterSchemaAssets makes the shared private response schema available to // prompt preparation. A family registrar can register it once for all consumers. func RegisterSchemaAssets(registry *llm.AssetRegistry) error { - return registry.RegisterSchemaFS(embeddedAssets, "assets/schemas") + assets, err := schemaAssetFS() + if err != nil { + return err + } + schemas, err := fs.Sub(assets, "schemas") + if err != nil { + return fmt.Errorf("scope entity reconciliation schemas: %w", err) + } + return registry.RegisterSchemaFS(schemas, ".") }