feat: voice-drift check can baseline on style-guide voice exemplars (#2179 slice 3)#2247
Merged
Conversation
…2179) Add a 'Drift baseline' mode to the style.voice-drift editorial check so an issue's prose fingerprint can be measured against the CHOSEN voice (the style guide's voice exemplars) instead of only the drafted-issue mean — closing the blind spot where a whole series that drifted the same way never flags. - computeExemplarBaseline() combines the voice exemplars into one fingerprint (>= 40 words to be a stable center, else null -> fall back to drafted). - computeVoiceDrift() gains baselineMode (drafted|exemplars|blended); the center shifts to the exemplar profile (or midpoint) while sigma stays the drafted spread, and reports the effective mode + exemplarBaselineUsed. - style.voice-drift declares series.styleGuide as a source (exemplar edits re-stale the finding) and a baselineMode config field; describeDrift labels the baseline honestly. - The Voice Fingerprint matrix view resolves the same config and renders the chosen-voice baseline as its own footer row. Third slice of #2179 (voice-fingerprint baseline hook). Refs #2179
…≈0 boundary (#2179) Review-gate findings on PR #2247: - getVoiceFingerprint reports baselineMode/exemplarBaselineUsed even on a gatedOff run (< minIssues) where the baseline never ran and series is {}. The matrix view now gates usesChosenVoice on !gatedOff so the intro copy + footer row fall back instead of promising a baseline that wasn't applied. - Document the σ≈0 boundary at the metric skip: a metric where every drafted issue shares the same value but sits far from the chosen-voice center wants a SERIES-level finding (different shape than the per-issue outliers), filed as #2248. The common same-direction-drift case (σ>0) is unaffected. Refs #2179
…d label (#2179) Codex review-gate findings on PR #2247: - getVoiceFingerprint resolved only the GLOBAL style.voice-drift config, so a per-series editorialCheckConfig override (baselineMode/threshold/wells) drove the editor's findings but NOT the matrix view — they disagreed. resolveVoiceDriftConfig now takes the series' editorialCheckConfig and overlays the override on the global stored config, mirroring applySeriesCheckConfig's merge. - The Voice Fingerprint intro copy described blended mode as 'the chosen voice (its voice exemplars)', but blended centers on the MIDPOINT of the drafted mean and the chosen voice. Intro copy + footer row now label blended distinctly. Refs #2179
…2179) Codex round-2 finding: resolveVoiceDriftConfig merged the RAW global config with the series override and let resolveCheckConfig collapse the whole blob to bare schema defaults if the merge was invalid — while the finding-emitting applySeriesCheckConfig keeps the resolved global on an invalid override. A bad per-series value (e.g. sigmaThreshold beyond the schema max) would make the matrix use the default 1.5 while findings used the global threshold, re-opening the matrix/finding disagreement. Now resolves the global first, then overlays the override on it, keeping the resolved global on parse failure — byte-for-byte the applySeriesCheckConfig contract. Refs #2179
This was referenced Jul 7, 2026
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
Third slice of #2179 (CWQE Phase 14) — the voice-fingerprint baseline hook. Slices 1 (#2232, exemplar fields + prompt injection) and the prose-craft/continuity slices already shipped; this wires the exemplars into the deterministic
style.voice-driftcheck.The check flags an issue whose prose fingerprint sits > σ·threshold from a baseline. That baseline was always the drafted-issue mean, which has a blind spot: if every issue drifted the same way, the mean drifts with them and nothing flags. This adds a Drift baseline setting so drift can be measured against the voice you chose — the style guide's voice exemplars.
What shipped
computeExemplarBaseline(voiceExemplars, opts)(server/lib/editorial/voiceFingerprint.js) — combines the exemplar passages into one fingerprint; returnsnull(→ drafted fallback) when the combined text is under ~40 words (too thin to center against).computeVoiceDriftgainsbaselineMode—drafted(default, unchanged),exemplars(center on the chosen-voice profile),blended(midpoint). In exemplar/blended mode the σ stays the drafted spread — only the center shifts — so an issue is judged against the chosen voice while still tolerating the series' natural per-metric variance. ReportsbaselineMode+exemplarBaselineUsed; falls back todrafted(and says so) when no usable exemplars exist.series[key].centeris added;describeDriftlabels the baseline honestly ("vs the style guide's chosen voice").style.voice-driftcheck (server/lib/editorial/checks/proseStyle.js) — declaresseries.styleGuideas a source (so an exemplar edit re-stales the finding, like its LLM siblings), adds abaselineModeconfig field (preprocessed to coerce any bad value todrafted), and passesctx.series.styleGuide.voiceExemplarsthrough.server/services/pipeline/voiceFingerprint.js+client/src/pages/PipelineVoiceFingerprint.jsx) — resolves the same config so the highlighted outliers agree with the editor's findings, and renders the chosen-voice baseline as its own footer row + tooltip.Partial ship
This is a slice, not the whole issue — the AI-assisted voice-discovery flow and Writers Room parity remain. So this PR uses
Refs #2179(notCloses), matching how slice 1 (#2232) shipped, and the claim markers are released so the remainder stays claimable.Refs #2179
Test plan
server/lib/editorial/voiceFingerprint.test.js— 43 tests:computeExemplarBaseline(combine / null / word-floor / wells), the three baseline modes + fallback + coercion incomputeVoiceDrift, baseline-awaredescribeDrift, and registry wiring (source declared, config parses, exemplar baseline surfaces drift the drafted mean hides).server/services/pipeline/voiceFingerprint.test.js— service threadsbaselineMode+ exemplars from the style guide; drafted default; exemplar re-center; thin-exemplar fallback.client/src/pages/PipelineVoiceFingerprint.test.jsx— chosen-voice baseline row + copy render when exemplars mode is active.server/lib/editorial/suite green (907 tests). DB-backed suites are unrun here (noportos_testprovisioned — they fail identically on clean main).