feat: AI voice-discovery flow for series style guide (#2179)#2251
Open
atomantic wants to merge 2 commits into
Open
feat: AI voice-discovery flow for series style guide (#2179)#2251atomantic wants to merge 2 commits into
atomantic wants to merge 2 commits into
Conversation
Adds a 'Discover voice' action to the series style-guide editor: one LLM
call writes the same invented scene beat in five distinct registers
(spare / lyric / wry / close-psychic / cinematic), presented side-by-side
so the author picks the series voice by ear. Picking files the passage
into voiceExemplars ('the tuning fork'); rejecting files it into
voiceAntiExemplars. Nothing persists until save (mirrors the New Series
concept flow), and the call fires only on explicit click (no
cold-bootstrap LLM work).
- server/lib/styleGuide.js: VOICE_REGISTERS / VOICE_REGISTER_IDS
- server/services/pipeline/seriesVoiceDiscover.js: discoverSeriesVoice()
(strips existing exemplars from context so a re-run doesn't homogenize)
- server/routes/pipeline/series.js: POST /series/:id/discover-voice
- data.reference/prompts/stages/pipeline-series-voice-discover.md (+ stage-config)
- client: discoverSeriesVoice API + candidate-picker UI
Fourth slice of #2179 (the phase headline); Writers Room parity ships separately.
Refs #2179
#2179) Review fixes (codex HIGH, claude LOW): - Add migration 174 to seed the new pipeline-series-voice-discover stage into existing installs. Boot runs migrations but NOT setup-data.js, so a pull + pm2 restart (vs update.sh) would otherwise leave the stage unregistered and 'Discover voice' would throw 'Stage not found'. Mirrors the makeSeedMigration pattern (048). - Drop the redundant seen Set in discoverSeriesVoice — the byRegister Map is already the seen-set (keep-first semantics unchanged). Refs #2179
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ships the AI-assisted voice-discovery flow — the phase headline of CWQE Phase 14 (#2179). Filling a voice exemplar from a blank page is the hard part, so this lets the author hear the voice instead of describing it.
A new Discover voice button on the series style-guide editor runs one LLM call that writes the same invented scene beat five ways — spare / lyric / wry / close-psychic / cinematic. Each candidate renders side-by-side with its register label; one click files it into
voiceExemplars("the tuning fork") or, for a wrong-register passage, intovoiceAntiExemplars. Both stay capped at 3, and the register rides along as the passage note.This is the natural next slice after #2232 (exemplar schema + prompt injection) and #2247 (voice-fingerprint baseline hook).
What shipped
server/lib/styleGuide.js—VOICE_REGISTERS/VOICE_REGISTER_IDS: the five distinct registers (stable ids + LLM-steering hints), shared by the service, prompt, and UI.server/services/pipeline/seriesVoiceDiscover.js—discoverSeriesVoice(): runs the stage, normalizes/dedupes candidates by register into canonical order, clamps passages to the exemplar char cap. Returns candidates without persisting (mirrorsgenerateSeriesConcept). Strips any existing exemplars from the prompt context so a re-run surfaces fresh contrast instead of echoing the already-picked voice.server/routes/pipeline/series.js—POST /pipeline/series/:id/discover-voice(Zod-validated provider/model overrides).data.reference/prompts/stages/pipeline-series-voice-discover.md+stage-config.jsonentry — a brand-new stage template, so no hash migration is needed (setup-data.jsseeds missing stage files + merges the new config key on existing installs).discoverSeriesVoiceAPI wrapper + the candidate-picker UI inPipelineSeries.jsx(flushes dirty edits before the call; explicit-click only, honoring the no-cold-bootstrap AI policy).Test plan
server/services/pipeline/seriesVoiceDiscover.test.js(new, 12 cases): register normalization / dedupe / canonical ordering, unknown-register + empty-passage dropping, char-cap clamp, empty-result error, context passing, no-universe tolerance, and the no-exemplar-leak regression.server/lib/styleGuide.test.js— addedVOICE_REGISTERSshape + uniqueness coverage.{{...}}.npm run build(client) ✅, ESLint clean, all pipeline route/service suites green (285 passed).Remaining on #2179 (out of scope for this slice)
writers-room-continue.md+ the Phase 9 polish loop.Refs #2179