From ab70347c5de1cffcd4ac008516542a2b0b000483 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 5 Aug 2026 14:42:54 +0000 Subject: [PATCH] Consolidate scene and normalization prompt instructions --- .../normalize/prompts/instructions.md | 6 +++ .../locations/normalize/prompts/prompt.yaml | 2 +- .../dnd/locations/normalize/prompts/task.md | 8 ---- .../npcs/normalize/prompts/instructions.md | 10 +++++ assets/dnd/npcs/normalize/prompts/prompt.yaml | 2 +- assets/dnd/npcs/normalize/prompts/task.md | 13 ------ .../prompts/instructions.md | 10 +++-- .../scene-descriptions/prompts/prompt.yaml | 2 - assets/dnd/scene-descriptions/prompts/task.md | 5 --- assets/dnd/scenes/prompts/instructions.md | 25 ++++++++--- assets/dnd/scenes/prompts/prompt.yaml | 2 - assets/dnd/scenes/prompts/task.md | 18 -------- .../modules/dnd/chunk/scenes/prompt_assets.go | 1 - .../dnd/chunk/scenes/prompt_assets_test.go | 43 +++++++++++-------- .../scenedescriptions/prompt_assets.go | 1 - .../scenedescriptions/prompt_assets_test.go | 34 ++++++++++++++- .../dnd/normalize/locations/prompt_assets.go | 2 +- .../normalize/locations/prompt_assets_test.go | 27 +++++++++++- .../dnd/normalize/npcs/prompt_assets.go | 2 +- .../dnd/normalize/npcs/prompt_assets_test.go | 22 +++++++--- 20 files changed, 142 insertions(+), 93 deletions(-) create mode 100644 assets/dnd/locations/normalize/prompts/instructions.md delete mode 100644 assets/dnd/locations/normalize/prompts/task.md create mode 100644 assets/dnd/npcs/normalize/prompts/instructions.md delete mode 100644 assets/dnd/npcs/normalize/prompts/task.md delete mode 100644 assets/dnd/scene-descriptions/prompts/task.md delete mode 100644 assets/dnd/scenes/prompts/task.md diff --git a/assets/dnd/locations/normalize/prompts/instructions.md b/assets/dnd/locations/normalize/prompts/instructions.md new file mode 100644 index 0000000..3042454 --- /dev/null +++ b/assets/dnd/locations/normalize/prompts/instructions.md @@ -0,0 +1,6 @@ +Use candidate names and their cited transcript windows to determine whether +candidates identify the same physical place. Do not treat matching names, +nearby evidence, nested places, or generic labels as sufficient. Keep parent +and child places, similarly named places, and uncertain aliases separate. + +When selecting a canonical display name, prefer the clearest established name. diff --git a/assets/dnd/locations/normalize/prompts/prompt.yaml b/assets/dnd/locations/normalize/prompts/prompt.yaml index b1827e2..e0ec2e2 100644 --- a/assets/dnd/locations/normalize/prompts/prompt.yaml +++ b/assets/dnd/locations/normalize/prompts/prompt.yaml @@ -12,7 +12,7 @@ messages: - role: system content_file: ./sharedassets/common-dnd-system.md - role: user - content_file: ./task.md + content_file: ./instructions.md - role: user content_file: ./sharedassets/common-dnd-entity-reconciliation.md cache_control: diff --git a/assets/dnd/locations/normalize/prompts/task.md b/assets/dnd/locations/normalize/prompts/task.md deleted file mode 100644 index b691e8e..0000000 --- a/assets/dnd/locations/normalize/prompts/task.md +++ /dev/null @@ -1,8 +0,0 @@ -Review location candidates and cited transcript context. Group candidates only -when the evidence clearly identifies one physical place. - -Do not group candidates solely because their names match, their evidence is -nearby, one place is nested inside another, or their labels are generic. Keep -parent and child places, similarly named places, and uncertain aliases -separate. For an accepted group, select the supplied candidate with the -clearest established display name as canonical. diff --git a/assets/dnd/npcs/normalize/prompts/instructions.md b/assets/dnd/npcs/normalize/prompts/instructions.md new file mode 100644 index 0000000..2ceba2d --- /dev/null +++ b/assets/dnd/npcs/normalize/prompts/instructions.md @@ -0,0 +1,10 @@ +Use candidate aliases and their cited transcript windows to determine whether +candidates refer to the same individual. Preserve distinct individuals even +when their names are similar. + +When selecting a canonical display name, prefer a complete, stable proper name +over an abbreviation. Prefer an unadorned proper name over that name plus a +contextual class, role, title, or relationship descriptor unless the transcript +establishes the descriptor as part of the person's name. A longer display name +is not inherently more canonical; for example, do not prefer `Captain Aria` +over `Aria` solely because it includes the contextual title `Captain`. diff --git a/assets/dnd/npcs/normalize/prompts/prompt.yaml b/assets/dnd/npcs/normalize/prompts/prompt.yaml index 7be5666..428dbe4 100644 --- a/assets/dnd/npcs/normalize/prompts/prompt.yaml +++ b/assets/dnd/npcs/normalize/prompts/prompt.yaml @@ -12,7 +12,7 @@ messages: - role: system content_file: ./sharedassets/common-dnd-system.md - role: user - content_file: ./task.md + content_file: ./instructions.md - role: user content_file: ./sharedassets/common-dnd-entity-reconciliation.md cache_control: diff --git a/assets/dnd/npcs/normalize/prompts/task.md b/assets/dnd/npcs/normalize/prompts/task.md deleted file mode 100644 index a7482f4..0000000 --- a/assets/dnd/npcs/normalize/prompts/task.md +++ /dev/null @@ -1,13 +0,0 @@ -Review NPC candidates and their cited transcript context to identify aliases -that refer to the same individual. Propose only groups supported by the -transcript, and preserve distinct individuals even when their names are -similar. - -For every accepted group, choose as canonical only a supplied candidate from -that evidence-supported duplicate group. Prefer a complete, stable proper name -over an abbreviation. Prefer an unadorned proper name over that name plus a -contextual class, role, title, or relationship descriptor unless the transcript -establishes the descriptor as part of the person's name. A longer display name -is not inherently more canonical; for example, do not prefer `Captain Aria` -over `Aria` solely because it includes the contextual title `Captain`. Do not -invent, edit, or combine display names. diff --git a/assets/dnd/scene-descriptions/prompts/instructions.md b/assets/dnd/scene-descriptions/prompts/instructions.md index d6be286..f105809 100644 --- a/assets/dnd/scene-descriptions/prompts/instructions.md +++ b/assets/dnd/scene-descriptions/prompts/instructions.md @@ -1,4 +1,9 @@ -Choose exactly one kind: +Describe exactly one accepted Dungeons & Dragons scene from the supplied +transcript chunk. The complete chunk is the evidence boundary: do not split it +into multiple scenes or use facts that are not supported by it. + +Return one kind, one concise title, and one concise summary. Choose exactly one +kind: - combat: active combat materially organizes the scene, including initiative-like exchanges or sustained hostile action. Planning a fight or @@ -39,6 +44,3 @@ outcome established within the accepted chunk. Do not add analysis, inferred motives, hidden state, future consequences, relationship claims, or facts from outside the chunk. Campaign references may disambiguate names but never add events or lore. - -Do not return identifiers, source identifiers, source ranges, unit identifiers, -participants, confidence, or any fields besides kind, title, and summary. diff --git a/assets/dnd/scene-descriptions/prompts/prompt.yaml b/assets/dnd/scene-descriptions/prompts/prompt.yaml index 22798b0..fc12786 100644 --- a/assets/dnd/scene-descriptions/prompts/prompt.yaml +++ b/assets/dnd/scene-descriptions/prompts/prompt.yaml @@ -27,8 +27,6 @@ messages: content_file: ./sharedassets/common-dnd-transcript-chunk.md cache_control: type: ephemeral - - role: user - content_file: ./task.md - role: user content_file: ./instructions.md cache_control: diff --git a/assets/dnd/scene-descriptions/prompts/task.md b/assets/dnd/scene-descriptions/prompts/task.md deleted file mode 100644 index 36ed668..0000000 --- a/assets/dnd/scene-descriptions/prompts/task.md +++ /dev/null @@ -1,5 +0,0 @@ -Describe exactly one accepted Dungeons & Dragons scene from the supplied -transcript chunk. The complete chunk is the evidence boundary: do not split it -into multiple scenes or use facts that are not supported by it. - -Return one kind, one concise title, and one concise summary. diff --git a/assets/dnd/scenes/prompts/instructions.md b/assets/dnd/scenes/prompts/instructions.md index 1ef7fa5..24ef37f 100644 --- a/assets/dnd/scenes/prompts/instructions.md +++ b/assets/dnd/scenes/prompts/instructions.md @@ -1,8 +1,19 @@ -Cover the complete provided transcript from its first source unit to its last -source unit. Return scenes in source-unit order with no gaps or overlaps. Use -only positive integer source-unit IDs from the transcript, and give every scene -one inclusive `start_unit_id` and one inclusive `end_unit_id`. +Divide the complete provided transcript into coherent Dungeons & Dragons scenes +for the `dnd/scenes` chunk module. -Return exactly one JSON object and no explanatory text. The object must contain -only a non-empty `scenes` array. Each scene object must contain only -`start_unit_id` and `end_unit_id`. +A scene is a coherent unit of play. Start a new scene when the transcript +establishes a meaningful change in location, objective, threat, activity, +encounter, or mode of play. Good reasons include a material move, beginning or +ending combat, a substantially different encounter phase, a shift between +combat, exploration, social interaction, planning, travel, rest, or downtime, +a change in the central NPC, faction, threat, or objective, or a sustained +table-level interruption that materially changes the activity. + +Do not split a scene merely because a speaker or combat round changes, a +routine turn occurs, or the table briefly digresses. Prefer fewer coherent +scenes over speculative or fine-grained boundaries. + +Cover the complete transcript from its first source unit to its last. Return +scenes in source-unit order with no gaps or overlaps. Use only positive integer +source-unit IDs from the transcript, and give every scene one inclusive +`start_unit_id` and one inclusive `end_unit_id`. diff --git a/assets/dnd/scenes/prompts/prompt.yaml b/assets/dnd/scenes/prompts/prompt.yaml index a0137b5..cf6c5c1 100644 --- a/assets/dnd/scenes/prompts/prompt.yaml +++ b/assets/dnd/scenes/prompts/prompt.yaml @@ -21,8 +21,6 @@ messages: content_file: ./sharedassets/common-dnd-references.md cache_control: type: ephemeral - - role: user - content_file: ./task.md - role: user content_file: ./instructions.md - role: user diff --git a/assets/dnd/scenes/prompts/task.md b/assets/dnd/scenes/prompts/task.md deleted file mode 100644 index 7ac9666..0000000 --- a/assets/dnd/scenes/prompts/task.md +++ /dev/null @@ -1,18 +0,0 @@ -Divide the provided transcript into coherent Dungeons & Dragons scenes for the -`dnd/scenes` chunk module. - -A scene is a coherent unit of play. Start a new scene when the transcript -establishes a meaningful change in location, objective, threat, activity, -encounter, or mode of play. Good reasons include a material move, beginning or -ending combat, a substantially different encounter phase, a shift between -combat, exploration, social interaction, planning, travel, rest, or downtime, -a change in the central NPC, faction, threat, or objective, or a sustained -table-level interruption that materially changes the activity. - -Do not split a scene merely because a speaker or combat round changes, a -routine turn occurs, or the table briefly digresses. Prefer fewer coherent -scenes over speculative or fine-grained boundaries. - -Return only inclusive `start_unit_id` and `end_unit_id` endpoints for each -scene. Do not return titles, modes, participants, summaries, boundary notes, -confidence, caveats, final chunk IDs, or chunk indexes. diff --git a/internal/modules/dnd/chunk/scenes/prompt_assets.go b/internal/modules/dnd/chunk/scenes/prompt_assets.go index 1767ce4..f9d3c90 100644 --- a/internal/modules/dnd/chunk/scenes/prompt_assets.go +++ b/internal/modules/dnd/chunk/scenes/prompt_assets.go @@ -17,7 +17,6 @@ var promptAssetManifest = shared.PromptAssetManifest{ ModuleDir: "dnd.scenes", ModuleFiles: []promptfs.ModulePromptFile{ {Name: "prompt.yaml", Path: "prompts/prompt.yaml"}, - {Name: "task.md", Path: "prompts/task.md"}, {Name: "instructions.md", Path: "prompts/instructions.md"}, }, SharedFiles: []string{ diff --git a/internal/modules/dnd/chunk/scenes/prompt_assets_test.go b/internal/modules/dnd/chunk/scenes/prompt_assets_test.go index 1010b09..18d6b59 100644 --- a/internal/modules/dnd/chunk/scenes/prompt_assets_test.go +++ b/internal/modules/dnd/chunk/scenes/prompt_assets_test.go @@ -12,7 +12,7 @@ import ( "gitea.maximumdirect.net/eric/promptkit" ) -func TestPromptAssetsPrepareTranscriptAndTaskMessages(t *testing.T) { +func TestPromptAssetsPrepareTranscriptWithInstructionOrdering(t *testing.T) { if !slices.Contains(promptAssetManifest.SharedFiles, "common-dnd-transcript-full.md") { t.Fatalf("shared prompt assets = %#v, want complete-session transcript scope", promptAssetManifest.SharedFiles) } @@ -22,35 +22,42 @@ func TestPromptAssetsPrepareTranscriptAndTaskMessages(t *testing.T) { if prepared.PromptID != PromptID { t.Fatalf("prompt id = %q, want %q", prepared.PromptID, PromptID) } - if len(prepared.Messages) != 5 { - t.Fatalf("prepared messages = %d, want 5", len(prepared.Messages)) + if len(prepared.Messages) < 4 { + t.Fatalf("prepared messages = %#v, want system, references, instructions, and transcript", prepared.Messages) } - for index, role := range []string{"system", "user", "user", "user", "user"} { - if prepared.Messages[index].Role != role { - t.Errorf("message %d role = %q, want %q", index, prepared.Messages[index].Role, role) - } + if prepared.Messages[0].Role != "system" || prepared.Messages[1].Role != "user" { + t.Fatalf("initial prompt roles = %#v, want system followed by campaign references", prepared.Messages[:2]) } - for _, index := range []int{1, 4} { - if cache := prepared.Messages[index].CacheControl; cache == nil || cache.Type != promptkit.CacheControlEphemeral { - t.Errorf("message %d cache control = %#v, want ephemeral", index, cache) - } + if cache := prepared.Messages[1].CacheControl; cache == nil || cache.Type != promptkit.CacheControlEphemeral { + t.Fatalf("campaign-reference cache control = %#v, want ephemeral", cache) } - for _, index := range []int{0, 2, 3} { - if cache := prepared.Messages[index].CacheControl; cache != nil { - t.Errorf("message %d cache control = %#v, want nil", index, cache) - } + if cache := prepared.Messages[0].CacheControl; cache != nil { + t.Fatalf("system cache control = %#v, want nil", cache) } if references := prepared.Messages[1].Content; !strings.Contains(references, "scene-players") || !strings.Contains(references, "scene-party") || !strings.Contains(references, "scene-glossary") { t.Fatalf("reference message = %q, want supplied reference inputs", references) } - if transcriptMessage := prepared.Messages[4].Content; !strings.Contains(transcriptMessage, "scene-transcript") { - t.Fatalf("final message = %q, want transcript input", transcriptMessage) + transcriptIndex := -1 + for index, message := range prepared.Messages { + if strings.Contains(message.Content, "scene-transcript") { + transcriptIndex = index + } + } + if transcriptIndex != len(prepared.Messages)-1 { + t.Fatalf("transcript message index = %d, want final message in %#v", transcriptIndex, prepared.Messages) + } + if cache := prepared.Messages[transcriptIndex].CacheControl; cache == nil || cache.Type != promptkit.CacheControlEphemeral { + t.Fatalf("transcript cache control = %#v, want ephemeral", cache) + } + instructionsIndex := transcriptIndex - 1 + if instructionsIndex <= 1 || prepared.Messages[instructionsIndex].CacheControl != nil { + t.Fatalf("instruction message = %#v, want uncached message after references and before transcript", prepared.Messages[instructionsIndex]) } for index, message := range prepared.Messages { if index != 1 && (strings.Contains(message.Content, "scene-players") || strings.Contains(message.Content, "scene-party") || strings.Contains(message.Content, "scene-glossary")) { t.Errorf("message %d unexpectedly rendered reference input", index) } - if index != 4 && strings.Contains(message.Content, "scene-transcript") { + if index != transcriptIndex && strings.Contains(message.Content, "scene-transcript") { t.Errorf("message %d unexpectedly rendered transcript input", index) } } diff --git a/internal/modules/dnd/extract/scenedescriptions/prompt_assets.go b/internal/modules/dnd/extract/scenedescriptions/prompt_assets.go index 6d8867a..3aea296 100644 --- a/internal/modules/dnd/extract/scenedescriptions/prompt_assets.go +++ b/internal/modules/dnd/extract/scenedescriptions/prompt_assets.go @@ -17,7 +17,6 @@ var promptAssetManifest = shared.PromptAssetManifest{ ModuleDir: "dnd.scene_descriptions", ModuleFiles: []promptfs.ModulePromptFile{ {Name: "prompt.yaml", Path: "prompts/prompt.yaml"}, - {Name: "task.md", Path: "prompts/task.md"}, {Name: "instructions.md", Path: "prompts/instructions.md"}, }, SharedFiles: []string{ diff --git a/internal/modules/dnd/extract/scenedescriptions/prompt_assets_test.go b/internal/modules/dnd/extract/scenedescriptions/prompt_assets_test.go index 5b5736a..ed10bfb 100644 --- a/internal/modules/dnd/extract/scenedescriptions/prompt_assets_test.go +++ b/internal/modules/dnd/extract/scenedescriptions/prompt_assets_test.go @@ -30,8 +30,8 @@ func TestRegisterPromptAssetsPreparesSceneDescriptionPrompt(t *testing.T) { prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{ PromptID: PromptID, PromptVersion: SchemaVersion, ProfileID: "scene-description-test-profile", Inputs: map[string]promptkit.ArtifactRef{ - "transcript": promptkit.InlineWithURI("file:///session.json", `{"units":[1]}`), - "players": promptkit.Inline(" "), + "transcript": promptkit.InlineWithURI("file:///session.json", `{"sentinel":"scene-description-transcript"}`), + "players": promptkit.Inline("scene-description-player"), "party": promptkit.Inline(" "), "glossary": promptkit.Inline(" "), }, @@ -42,6 +42,36 @@ func TestRegisterPromptAssetsPreparesSceneDescriptionPrompt(t *testing.T) { if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_scene_descriptions_llm.v1.json" { t.Fatalf("prepared prompt = %#v, want scene-description prompt identity and schema wiring", prepared) } + if len(prepared.Messages) < 5 { + t.Fatalf("prepared messages = %#v, want shared policy, references, transcript, and instructions", prepared.Messages) + } + referenceIndex := -1 + transcriptIndex := -1 + for index, message := range prepared.Messages { + if strings.Contains(message.Content, "scene-description-player") { + referenceIndex = index + } + if strings.Contains(message.Content, "scene-description-transcript") { + transcriptIndex = index + } + } + instructionsIndex := len(prepared.Messages) - 1 + if referenceIndex < 0 || transcriptIndex <= referenceIndex || transcriptIndex >= instructionsIndex { + t.Fatalf("message order = references %d, transcript %d, instructions %d; want that order", referenceIndex, transcriptIndex, instructionsIndex) + } + for _, index := range []int{referenceIndex, transcriptIndex, instructionsIndex} { + if cache := prepared.Messages[index].CacheControl; cache == nil || cache.Type != promptkit.CacheControlEphemeral { + t.Fatalf("message %d cache control = %#v, want ephemeral", index, cache) + } + } + for index, message := range prepared.Messages { + if index != referenceIndex && strings.Contains(message.Content, "scene-description-player") { + t.Errorf("message %d unexpectedly rendered campaign-reference input", index) + } + if index != transcriptIndex && strings.Contains(message.Content, "scene-description-transcript") { + t.Errorf("message %d unexpectedly rendered transcript input", index) + } + } } func TestPromptMetadataAndDiagnosticsDoNotContainRawAssets(t *testing.T) { diff --git a/internal/modules/dnd/normalize/locations/prompt_assets.go b/internal/modules/dnd/normalize/locations/prompt_assets.go index f15b1f6..6697487 100644 --- a/internal/modules/dnd/normalize/locations/prompt_assets.go +++ b/internal/modules/dnd/normalize/locations/prompt_assets.go @@ -17,7 +17,7 @@ var promptAssetManifest = shared.PromptAssetManifest{ ModuleDir: PromptID, ModuleFiles: []promptfs.ModulePromptFile{ {Name: "prompt.yaml", Path: "prompts/prompt.yaml"}, - {Name: "task.md", Path: "prompts/task.md"}, + {Name: "instructions.md", Path: "prompts/instructions.md"}, {Name: "candidates.md", Path: "prompts/candidates.md"}, }, SharedFiles: []string{"common-dnd-system.md", "common-dnd-entity-reconciliation.md", "common-dnd-transcript-windows.md"}, diff --git a/internal/modules/dnd/normalize/locations/prompt_assets_test.go b/internal/modules/dnd/normalize/locations/prompt_assets_test.go index 2a93f1a..a757ec8 100644 --- a/internal/modules/dnd/normalize/locations/prompt_assets_test.go +++ b/internal/modules/dnd/normalize/locations/prompt_assets_test.go @@ -12,6 +12,18 @@ import ( ) func TestRegisterPromptAssetsPreparesLocationNormalizationPrompt(t *testing.T) { + hasInstructions := false + for _, file := range promptAssetManifest.ModuleFiles { + if file.Name == "task.md" { + t.Fatalf("module prompt files = %#v, want no retired task asset", promptAssetManifest.ModuleFiles) + } + if file.Name == "instructions.md" { + hasInstructions = true + } + } + if !hasInstructions { + t.Fatalf("module prompt files = %#v, want instructions asset", promptAssetManifest.ModuleFiles) + } registry := llm.NewAssetRegistry() if err := entityreconcile.RegisterSchemaAssets(registry); err != nil { t.Fatal(err) @@ -32,15 +44,26 @@ func TestRegisterPromptAssetsPreparesLocationNormalizationPrompt(t *testing.T) { if err != nil { t.Fatal(err) } - if prepared.OutputContract.SchemaPath != "dnd_entity_reconcile_llm.v1.json" || len(prepared.Messages) != 5 { + if prepared.OutputContract.SchemaPath != "dnd_entity_reconcile_llm.v1.json" || len(prepared.Messages) < 5 { t.Fatalf("prepared prompt = %#v", prepared) } for _, index := range []int{2, 4} { - if prepared.Messages[index].CacheControl == nil { + if prepared.Messages[index].CacheControl == nil || prepared.Messages[index].CacheControl.Type != promptkit.CacheControlEphemeral { t.Fatalf("message %d cache = %#v", index, prepared.Messages[index].CacheControl) } } if !strings.Contains(prepared.Messages[3].Content, "candidate-000001") || strings.Contains(prepared.Messages[3].Content, `"windows"`) { t.Fatalf("candidate message = %q", prepared.Messages[3].Content) } + if !strings.Contains(prepared.Messages[4].Content, `"windows"`) || strings.Contains(prepared.Messages[4].Content, "candidate-000001") { + t.Fatalf("transcript message = %q", prepared.Messages[4].Content) + } + for index, message := range prepared.Messages { + if index != 3 && strings.Contains(message.Content, "candidate-000001") { + t.Errorf("message %d unexpectedly rendered candidate input", index) + } + if index != 4 && strings.Contains(message.Content, `"windows"`) { + t.Errorf("message %d unexpectedly rendered transcript input", index) + } + } } diff --git a/internal/modules/dnd/normalize/npcs/prompt_assets.go b/internal/modules/dnd/normalize/npcs/prompt_assets.go index 6b8571c..576850d 100644 --- a/internal/modules/dnd/normalize/npcs/prompt_assets.go +++ b/internal/modules/dnd/normalize/npcs/prompt_assets.go @@ -17,7 +17,7 @@ var promptAssetManifest = shared.PromptAssetManifest{ ModuleDir: PromptID, ModuleFiles: []promptfs.ModulePromptFile{ {Name: "prompt.yaml", Path: "prompts/prompt.yaml"}, - {Name: "task.md", Path: "prompts/task.md"}, + {Name: "instructions.md", Path: "prompts/instructions.md"}, {Name: "candidates.md", Path: "prompts/candidates.md"}, }, SharedFiles: []string{ diff --git a/internal/modules/dnd/normalize/npcs/prompt_assets_test.go b/internal/modules/dnd/normalize/npcs/prompt_assets_test.go index 098ccc8..825cceb 100644 --- a/internal/modules/dnd/normalize/npcs/prompt_assets_test.go +++ b/internal/modules/dnd/normalize/npcs/prompt_assets_test.go @@ -16,6 +16,18 @@ func TestRegisterPromptAssetsPreparesNormalizationPrompt(t *testing.T) { if want := []string{"common-dnd-system.md", "common-dnd-entity-reconciliation.md", "common-dnd-transcript-windows.md"}; !reflect.DeepEqual(promptAssetManifest.SharedFiles, want) { t.Fatalf("shared prompt assets = %#v, want %#v", promptAssetManifest.SharedFiles, want) } + hasInstructions := false + for _, file := range promptAssetManifest.ModuleFiles { + if file.Name == "task.md" { + t.Fatalf("module prompt files = %#v, want no retired task asset", promptAssetManifest.ModuleFiles) + } + if file.Name == "instructions.md" { + hasInstructions = true + } + } + if !hasInstructions { + t.Fatalf("module prompt files = %#v, want instructions asset", promptAssetManifest.ModuleFiles) + } if promptHash, err := promptAssetMetadata(); err != nil || promptHash == "" { t.Fatalf("promptAssetMetadata() = %q, %v; want prompt fingerprint", promptHash, err) } @@ -50,13 +62,11 @@ func TestRegisterPromptAssetsPreparesNormalizationPrompt(t *testing.T) { if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_entity_reconcile_llm.v1.json" { t.Fatalf("prepared prompt = %#v, want normalization prompt identity and schema", prepared) } - if len(prepared.Messages) != 5 { - t.Fatalf("prepared messages = %d, want 5", len(prepared.Messages)) + if len(prepared.Messages) < 5 { + t.Fatalf("prepared messages = %#v, want system, instructions, reconciliation policy, candidates, and transcript windows", prepared.Messages) } - for index, role := range []string{"system", "user", "user", "user", "user"} { - if prepared.Messages[index].Role != role { - t.Errorf("message %d role = %q, want %q", index, prepared.Messages[index].Role, role) - } + if prepared.Messages[0].Role != "system" { + t.Fatalf("initial message role = %q, want system", prepared.Messages[0].Role) } for _, index := range []int{2, 4} { if cache := prepared.Messages[index].CacheControl; cache == nil || cache.Type != promptkit.CacheControlEphemeral {