Files
notarius/docs/roadmap/implementation.md

278 lines
11 KiB
Markdown

# D&D Shared Helper Refactor Implementation Plan
## Summary
Implement the target state defined in
[D&D Shared Module Helper Roadmap](dnd.md). 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 `CloneReferenceSlots` to `internal/framework/contracts`.
- The helper must:
- return `nil` for an empty input slice;
- allocate a new slot slice;
- deep-copy each `AcceptedMediaTypes` slice;
- preserve all other `ReferenceSlot` fields exactly.
- Add focused tests in `internal/framework/contracts` proving 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.ModulePromptFS` so the parent package
no longer knows about D&D prompt filenames or embedded D&D assets.
- Add a generic shared prompt file descriptor:
```go
type SharedPromptFile struct {
Name string
FS fs.FS
Path string
}
```
- Change `ModulePromptFS` to this shape:
```go
func ModulePromptFS(
moduleDir string,
moduleFS fs.FS,
files []ModulePromptFile,
sharedFiles ...SharedPromptFile,
) (fs.FS, error)
```
- Keep existing `ModulePromptFile` semantics:
- `Name` is mounted directly under `assets/prompts/<moduleDir>/`;
- `Path` is read from the module embedded filesystem.
- Mount each `SharedPromptFile` under
`assets/prompts/<moduleDir>/sharedassets/<Name>`.
- Validate all names and paths with contextual errors:
- `moduleDir` must be a valid non-root fs path;
- module prompt `Name` must be a single filename with no path separators;
- shared prompt `Name` must be a single filename with no path separators;
- `moduleFS` and each shared file `FS` must 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/sharedassets` tests so they use test-owned shared
prompt files from `fstest.MapFS`, not production D&D prompt assets.
- Remove D&D-specific helpers, constants, and embedded asset dependencies from
the parent `sharedassets` package.
## Stage 3: Create `sharedassets/dnd`
- Create `internal/modules/sharedassets/dnd` with package name `dnd`.
- Move the current D&D shared prompt files into:
```text
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.go` in the D&D package with `go:embed assets/prompts/*.md`.
- Expose D&D shared asset helpers:
```go
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)
```
- `SharedPromptFiles` must return a new slice each call.
- `CommonHashParts` must include `common-dnd-system.md` and
`common-dnd-transcript.md`.
- `ReferenceHashParts` must include `common-dnd-references.md`.
- `ModulePromptFS` must call `sharedassets.ModulePromptFS` with
`SharedPromptFiles()...`.
- 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:
```go
type ReferenceSlotDescriptions struct {
Glossary string
Party string
Players string
Roster string
}
func ReferenceMediaTypes() []string
func ReferenceSlots(descriptions ReferenceSlotDescriptions) []contracts.ReferenceSlot
```
- `ReferenceMediaTypes` must return a defensive copy of:
- `application/json`
- `application/x-yaml`
- `application/yaml`
- `text/markdown`
- `text/plain`
- `ReferenceSlots` must 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
`ReferenceSlotDescriptions` so current module metadata text can be preserved.
- `ReferenceSlots` must use `contracts.CloneReferenceSlots` or equivalent
defensive-copy behavior before returning.
- Add prompt input helpers:
```go
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
```
- `PromptInputs` must return inputs named `transcript`, `players`, `party`, and
`glossary`.
- If `party` has no items and `roster` has items, `PromptInputs` must use the
`roster` slot content for the `party` input.
- `PromptInputs` must not include a `roster` prompt input.
- `TranscriptPromptMaterial` must clone the source input and set `Name` to
`transcript`.
- `ReferencePromptMaterial` must use media type `text/plain`; for a single
reference item it must propagate that item's digest and origin URI.
- `ReferencePromptInput` behavior 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/scenes` and `dnd/spells`.
- Add focused tests under `internal/modules/sharedassets/dnd` for 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.ModulePromptFS` with `dnd.ModulePromptFS`;
- replace `sharedassets.CommonHashParts` and `ReferenceHashParts` with the
D&D package equivalents.
- Update `internal/modules/extract/dnd/spells` the 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/cli` as needed:
- remove any call that registers D&D shared prompt files from the generic
`sharedassets` package;
- ensure `dnd/scenes` and `dnd/spells` prompt registration still makes their
module-local `./sharedassets/common-dnd-*.md` files available.
- Update production prompt asset tests so they assert module-local shared asset
paths such as:
- `dnd.scenes/sharedassets/common-dnd-system.md`
- `dnd.spells/sharedassets/common-dnd-system.md`
- Remove expectations for top-level `common-dnd-*.md` prompt 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 under `internal/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.md` after 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:
```sh
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:
```sh
go test ./...
go vet ./...
go build ./cmd/notarius
```
Run inspection searches:
```sh
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 `sharedassets` has no hard-coded `common-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`.