11 KiB
D&D Shared Helper Refactor Implementation Plan
Summary
Implement the target state defined in D&D Shared Module Helper Roadmap. This is an internal refactor: do not change module keys, prompt IDs, prompt versions, Scriptorium-visible message order, response schema IDs or names, CLI/config semantics, manifest shape, diagnostics redaction policy, or D&D scene/spell interpretation behavior.
The intended package split is:
internal/modules/sharedassets: generic prompt filesystem composition and non-domain-specific asset plumbing only;internal/modules/sharedassets/dnd: reusable D&D prompt assets, prompt input helpers, reference slot helpers, reference rendering, and D&D shared prompt hash parts;- concrete D&D modules: stage contracts, module registration, module-local prompt definitions, response schemas, validators, and response interpretation.
Stage 1: Add Generic Defensive-Copy Helper
- Add
CloneReferenceSlotstointernal/framework/contracts. - The helper must:
- return
nilfor an empty input slice; - allocate a new slot slice;
- deep-copy each
AcceptedMediaTypesslice; - preserve all other
ReferenceSlotfields exactly.
- return
- Add focused tests in
internal/framework/contractsproving nil/empty behavior, deep-copy behavior, and full field preservation. - Do not import any concrete module package from
contracts.
Stage 2: Make Shared Prompt FS Composition Domain-Neutral
- Refactor
internal/modules/sharedassets.ModulePromptFSso the parent package no longer knows about D&D prompt filenames or embedded D&D assets. - Add a generic shared prompt file descriptor:
type SharedPromptFile struct {
Name string
FS fs.FS
Path string
}
- Change
ModulePromptFSto this shape:
func ModulePromptFS(
moduleDir string,
moduleFS fs.FS,
files []ModulePromptFile,
sharedFiles ...SharedPromptFile,
) (fs.FS, error)
- Keep existing
ModulePromptFilesemantics:Nameis mounted directly underassets/prompts/<moduleDir>/;Pathis read from the module embedded filesystem.
- Mount each
SharedPromptFileunderassets/prompts/<moduleDir>/sharedassets/<Name>. - Validate all names and paths with contextual errors:
moduleDirmust be a valid non-root fs path;- module prompt
Namemust be a single filename with no path separators; - shared prompt
Namemust be a single filename with no path separators; moduleFSand each shared fileFSmust be non-nil;- missing module or shared files must return errors naming the source path.
- Preserve the in-memory FS behavior currently covered by tests, including
readable intermediate directories and root
.support. - Update
internal/modules/sharedassetstests so they use test-owned shared prompt files fromfstest.MapFS, not production D&D prompt assets. - Remove D&D-specific helpers, constants, and embedded asset dependencies from
the parent
sharedassetspackage.
Stage 3: Create sharedassets/dnd
- Create
internal/modules/sharedassets/dndwith package namednd. - Move the current D&D shared prompt files into:
internal/modules/sharedassets/dnd/assets/prompts/common-dnd-system.md
internal/modules/sharedassets/dnd/assets/prompts/common-dnd-transcript.md
internal/modules/sharedassets/dnd/assets/prompts/common-dnd-references.md
- Add
assets.goin the D&D package withgo:embed assets/prompts/*.md. - Expose D&D shared asset helpers:
func SharedPromptFiles() []sharedassets.SharedPromptFile
func CommonHashParts() []llm.AssetHashPart
func ReferenceHashParts() []llm.AssetHashPart
func ModulePromptFS(moduleDir string, moduleFS fs.FS, files []sharedassets.ModulePromptFile) (fs.FS, error)
SharedPromptFilesmust return a new slice each call.CommonHashPartsmust includecommon-dnd-system.mdandcommon-dnd-transcript.md.ReferenceHashPartsmust includecommon-dnd-references.md.ModulePromptFSmust callsharedassets.ModulePromptFSwithSharedPromptFiles()....- Do not expose a D&D package registration function unless a concrete caller
needs top-level D&D shared prompt files outside module-local prompt
composition. The D&D prompt definitions should continue to load shared files
through
./sharedassets/...inside each module prompt directory.
Stage 4: Add D&D Reference And Prompt Input Helpers
- In
internal/modules/sharedassets/dnd, add D&D reference helpers:
type ReferenceSlotDescriptions struct {
Glossary string
Party string
Players string
Roster string
}
func ReferenceMediaTypes() []string
func ReferenceSlots(descriptions ReferenceSlotDescriptions) []contracts.ReferenceSlot
-
ReferenceMediaTypesmust return a defensive copy of:application/jsonapplication/x-yamlapplication/yamltext/markdowntext/plain
-
ReferenceSlotsmust return slots in the same order currently exposed by both D&D modules:glossary,party,players,roster. -
Slot names and alias semantics must remain unchanged.
-
Slot descriptions should be supplied by each concrete module through
ReferenceSlotDescriptionsso current module metadata text can be preserved. -
ReferenceSlotsmust usecontracts.CloneReferenceSlotsor equivalent defensive-copy behavior before returning. -
Add prompt input helpers:
func PromptInputs(sourceInput contracts.LLMInputMaterial, references contracts.ReferenceSet) contracts.LLMInputSet
func TranscriptPromptMaterial(material contracts.LLMInputMaterial) contracts.LLMInputMaterial
func ReferencePromptMaterial(name string, slot contracts.ResolvedReferenceSlot) contracts.LLMInputMaterial
func ReferencePromptInput(slot contracts.ResolvedReferenceSlot) []byte
-
PromptInputsmust return inputs namedtranscript,players,party, andglossary. -
If
partyhas no items androsterhas items,PromptInputsmust use therosterslot content for thepartyinput. -
PromptInputsmust not include arosterprompt input. -
TranscriptPromptMaterialmust clone the source input and setNametotranscript. -
ReferencePromptMaterialmust use media typetext/plain; for a single reference item it must propagate that item's digest and origin URI. -
ReferencePromptInputbehavior must match current D&D module behavior:- empty slot renders as a single space;
- one item renders as raw item content;
- multiple items are copied, sorted deterministically by origin URI, digest,
then content, and rendered with the same heading/metadata format currently
used by
dnd/scenesanddnd/spells.
-
Add focused tests under
internal/modules/sharedassets/dndfor all helper behavior. These tests may assert rendering of helper-owned fixture inputs, but must not assert exact production embedded prompt prose.
Stage 5: Update D&D Modules To Use Shared Helpers
- Update
internal/modules/chunk/dnd/scenes:- import
internal/modules/sharedassets/dnd; - replace local accepted media type and slot cloning logic with
dnd.ReferenceSlots; - preserve existing scene-specific slot descriptions;
- replace local prompt input/reference rendering helpers with
dnd.PromptInputs; - replace
sharedassets.ModulePromptFSwithdnd.ModulePromptFS; - replace
sharedassets.CommonHashPartsandReferenceHashPartswith the D&D package equivalents.
- import
- Update
internal/modules/extract/dnd/spellsthe same way, preserving current extractor-specific slot descriptions. - Delete local duplicated helper functions that become unused:
acceptedReferenceMediaTypes;cloneReferenceSlots;transcriptPromptInput;referencePromptMaterial;referencePromptInput.
- Keep module-local schema loading, prompt IDs, prompt versions, validators, request validation, response conversion, and manifest metadata ownership in each concrete module.
Stage 6: Update Asset Registration And Tests
- Update production prompt asset registration in
internal/clias needed:- remove any call that registers D&D shared prompt files from the generic
sharedassetspackage; - ensure
dnd/scenesanddnd/spellsprompt registration still makes their module-local./sharedassets/common-dnd-*.mdfiles available.
- remove any call that registers D&D shared prompt files from the generic
- Update production prompt asset tests so they assert module-local shared asset
paths such as:
dnd.scenes/sharedassets/common-dnd-system.mddnd.spells/sharedassets/common-dnd-system.md
- Remove expectations for top-level
common-dnd-*.mdprompt files unless a genuine top-level registration remains necessary. - Move reference rendering tests from concrete D&D modules into
internal/modules/sharedassets/dnd. - Keep concrete D&D module tests focused on:
- module specs and registry behavior;
- LLM request prompt ID/version/profile/session fields;
- transcript and reference input propagation;
- Scriptorium prompt preparation;
- diagnostics redaction;
- manifest metadata;
- scene/spell response interpretation.
- Follow the prompt asset testing policy in
docs/policy/development.md: do not assert exact production embedded prompt prose.
Stage 7: Update Documentation
- Update canonical internal docs for implemented behavior:
docs/internal/modules.md: mention that common D&D prompt/reference helper behavior lives underinternal/modules/sharedassets/dnd;docs/internal/overview.md: mention the package only if the overview lists shared module-support packages.
- Do not add user-facing documentation unless visible behavior changes.
- Update
docs/roadmap/dnd.mdafter implementation so it no longer presents this refactor as future work. - Replace this file with a concise completed-note document once the work is implemented.
Validation
Run focused tests after each meaningful stage:
go test ./internal/framework/contracts
go test ./internal/modules/sharedassets
go test ./internal/modules/sharedassets/dnd
go test ./internal/modules/chunk/dnd/scenes
go test ./internal/modules/extract/dnd/spells
go test ./internal/cli
Run full validation before completion:
go test ./...
go vet ./...
go build ./cmd/notarius
Run inspection searches:
rg -n "common-dnd|CommonHashParts|ReferenceHashParts|referencePromptInput|cloneReferenceSlots|acceptedReferenceMediaTypes" internal/modules internal/framework
rg -n "Divide the provided transcript|Extract Dungeons & Dragons spell-cast artifacts|A transcript of a Dungeons & Dragons gameplay session|Roster reference|Glossary reference" internal/**/*_test.go
Expected inspection outcomes:
- D&D shared prompt files and hash helpers are owned by
internal/modules/sharedassets/dnd; - parent
sharedassetshas no hard-codedcommon-dnd-*knowledge; - concrete D&D modules no longer carry duplicated reference rendering logic;
- tests do not assert exact production embedded prompt prose.
Non-Goals
- Do not change D&D prompt wording except for file moves required by this refactor.
- Do not change prompt IDs, prompt versions, profile defaults, Scriptorium message order, schema files, schema metadata, module keys, capabilities, manifest shapes, or diagnostics policy.
- Do not move scene boundary logic, spell artifact logic, validators, or schema loading into the shared D&D package.
- Do not make framework, core, or CLI packages depend on
internal/modules/sharedassets/dnd.