From 6077fe120f8106b97edbbc6ab74d506e6be1044a Mon Sep 17 00:00:00 2001 From: "Dylan Mordaunt (ISLHD)" Date: Wed, 12 Aug 2026 05:30:17 +1000 Subject: [PATCH] feat(voice): wire sample precedence and non-fabrication Co-authored-by: Cursor --- SKILL.md | 4 +- .../metadata.json | 2 +- .../voice-corpus-calibration_20260811/plan.md | 14 +++-- references/core-patterns.md | 4 +- scripts/lib/document-intake-policy.js | 60 +++++++++++++++++++ src/modules/SKILL_CORE_PATTERNS.md | 4 +- test/document-intake-policy.test.js | 57 ++++++++++++++++++ test/voice-corpus-calibration.test.js | 18 ++++++ 8 files changed, 153 insertions(+), 10 deletions(-) diff --git a/SKILL.md b/SKILL.md index cbe1f69..73f4449 100644 --- a/SKILL.md +++ b/SKILL.md @@ -106,6 +106,8 @@ If the user supplies a writing sample of their own previous prose, analyse it be A sample outranks Authentext style rules, including the Pattern 13 dash ban: if the sample uses em dashes, keep them at roughly the sample's frequency. Matching the author beats scrubbing the tell. The sample does not outrank Never add, Never lose, or protected spans. +Facts found in the corpus may inform voice. They may be cited back as "you have written this before." They must not be inserted into the current document unless the user asks and the current source already supports the claim. A corpus does not license fabrication or invented biography. + The sample may be pasted text, or an explicit pointer the user names: - Local file: a path such as `./drafts/column.md` @@ -161,7 +163,7 @@ Voice comes from stance and rhythm, never from invented biography. The most comm Where prose is flat because it lacks concrete detail, the fix is to ask the writer for the detail, or to flag the gap. It is not to supply one. Stance, reaction, uncertainty, and cadence are yours to add. Facts and history are not. -Skip this section entirely for clinical, legal, regulatory, and submitted academic material, where first-person texture is not the register and the added-voice failure mode is expensive. +Skip this section entirely for clinical, legal, regulatory, and submitted academic material, where first-person texture is not the register and the added-voice failure mode is expensive. Skip it even when a first-person corpus is available. Do not import memoir texture, "I", or invented biography into those registers. ### Before (clean but soulless) diff --git a/conductor/tracks/voice-corpus-calibration_20260811/metadata.json b/conductor/tracks/voice-corpus-calibration_20260811/metadata.json index 3ad523c..7f3d0dc 100644 --- a/conductor/tracks/voice-corpus-calibration_20260811/metadata.json +++ b/conductor/tracks/voice-corpus-calibration_20260811/metadata.json @@ -7,7 +7,7 @@ "status": "in_progress", "created_at": "2026-08-11", "updated_at": "2026-08-12", - "current_phase": "Phase 3: Optional plugin sources", + "current_phase": "Phase 4: Precedence wiring", "parallel_safe": false, "depends_on": ["editorial-safety-invariants_20260811"], "coordinates_with": ["pattern-registry-contracts_20260811"], diff --git a/conductor/tracks/voice-corpus-calibration_20260811/plan.md b/conductor/tracks/voice-corpus-calibration_20260811/plan.md index 00ea01f..44e56cd 100644 --- a/conductor/tracks/voice-corpus-calibration_20260811/plan.md +++ b/conductor/tracks/voice-corpus-calibration_20260811/plan.md @@ -23,16 +23,18 @@ ## Phase 3: Optional plugin sources -- [~] Task: Write failing tests that email and other plugins are off by - default and require a named grant. -- [~] Task: Add a thin adapter interface. Do not ship a mail client. -- [ ] Task: Phase Verification & Checkpoint (Refer to `workflow.md`). +- [x] Task: Write failing tests that email and other plugins are off by + default and require a named grant. (commit: 488a3af) +- [x] Task: Add a thin adapter interface. Do not ship a mail client. + (commit: 488a3af) +- [x] Task: Phase Verification & Checkpoint (Refer to `workflow.md`). + [checkpoint: fa40812] ## Phase 4: Precedence wiring -- [ ] Task: Write failing tests that a calibrated sample outranks the dash +- [~] Task: Write failing tests that a calibrated sample outranks the dash ban and that corpus facts cannot be inserted into the current draft. -- [ ] Task: Wire precedence in skill prose and in the intake/diagnostic +- [~] Task: Wire precedence in skill prose and in the intake/diagnostic contracts. - [ ] Task: Phase Verification & Checkpoint (Refer to `workflow.md`). diff --git a/references/core-patterns.md b/references/core-patterns.md index 91f3622..d39af04 100644 --- a/references/core-patterns.md +++ b/references/core-patterns.md @@ -116,6 +116,8 @@ If the user supplies a writing sample of their own previous prose, analyse it be A sample outranks Authentext style rules, including the Pattern 13 dash ban: if the sample uses em dashes, keep them at roughly the sample's frequency. Matching the author beats scrubbing the tell. The sample does not outrank Never add, Never lose, or protected spans. +Facts found in the corpus may inform voice. They may be cited back as "you have written this before." They must not be inserted into the current document unless the user asks and the current source already supports the claim. A corpus does not license fabrication or invented biography. + The sample may be pasted text, or an explicit pointer the user names: - Local file: a path such as `./drafts/column.md` @@ -171,7 +173,7 @@ Voice comes from stance and rhythm, never from invented biography. The most comm Where prose is flat because it lacks concrete detail, the fix is to ask the writer for the detail, or to flag the gap. It is not to supply one. Stance, reaction, uncertainty, and cadence are yours to add. Facts and history are not. -Skip this section entirely for clinical, legal, regulatory, and submitted academic material, where first-person texture is not the register and the added-voice failure mode is expensive. +Skip this section entirely for clinical, legal, regulatory, and submitted academic material, where first-person texture is not the register and the added-voice failure mode is expensive. Skip it even when a first-person corpus is available. Do not import memoir texture, "I", or invented biography into those registers. ### Before (clean but soulless) diff --git a/scripts/lib/document-intake-policy.js b/scripts/lib/document-intake-policy.js index 8047192..c3e0d14 100644 --- a/scripts/lib/document-intake-policy.js +++ b/scripts/lib/document-intake-policy.js @@ -499,3 +499,63 @@ export function selectMaterialQuestion(intake = {}) { }, }; } + +const RESTRICTED_VOICE_CLASSES = new Set(['clinical', 'legal', 'regulatory', 'submitted-academic']); + +/** + * Decide whether a calibrated sample may override a style rule, and whether a + * corpus fact may enter the current draft. Never-add, never-lose, and + * protected spans still win. + * @param {{ + * sampleUsesEmDash?: boolean, + * styleRule?: string, + * corpusFact?: string, + * currentSourceSupports?: boolean, + * userAsked?: boolean, + * protectedSpan?: boolean, + * neverAddConflict?: boolean, + * neverLoseConflict?: boolean, + * documentClass?: string, + * }} [input] + */ +export function resolveVoicePrecedence({ + sampleUsesEmDash = false, + styleRule = 'dash-ban', + corpusFact, + currentSourceSupports = false, + userAsked = false, + protectedSpan = false, + neverAddConflict = false, + neverLoseConflict = false, + documentClass, +} = {}) { + const styleOverride = + styleRule === 'dash-ban' && sampleUsesEmDash === true ? 'keep-sample-dash' : 'apply-style-rule'; + + let insert = false; + let insertReason = 'corpus-fact-not-requested'; + if (protectedSpan === true) { + insertReason = 'protected-span'; + } else if (neverAddConflict === true) { + insertReason = 'never-add'; + } else if (neverLoseConflict === true) { + insertReason = 'never-lose'; + } else if (corpusFact && userAsked === true && currentSourceSupports === true) { + insert = true; + insertReason = 'user-asked-and-source-supports'; + } else if (corpusFact && userAsked === true && currentSourceSupports !== true) { + insertReason = 'current-source-lacks-claim'; + } else if (corpusFact) { + insertReason = 'corpus-does-not-license-fabrication'; + } + + return { + style_override: styleOverride, + may_insert_corpus_fact: insert, + insert_reason: insertReason, + skip_personality: + typeof documentClass === 'string' && RESTRICTED_VOICE_CLASSES.has(documentClass), + skip_first_person_texture: + typeof documentClass === 'string' && RESTRICTED_VOICE_CLASSES.has(documentClass), + }; +} diff --git a/src/modules/SKILL_CORE_PATTERNS.md b/src/modules/SKILL_CORE_PATTERNS.md index 17d5112..a6e2a43 100644 --- a/src/modules/SKILL_CORE_PATTERNS.md +++ b/src/modules/SKILL_CORE_PATTERNS.md @@ -110,6 +110,8 @@ If the user supplies a writing sample of their own previous prose, analyse it be A sample outranks Authentext style rules, including the Pattern 13 dash ban: if the sample uses em dashes, keep them at roughly the sample's frequency. Matching the author beats scrubbing the tell. The sample does not outrank Never add, Never lose, or protected spans. +Facts found in the corpus may inform voice. They may be cited back as "you have written this before." They must not be inserted into the current document unless the user asks and the current source already supports the claim. A corpus does not license fabrication or invented biography. + The sample may be pasted text, or an explicit pointer the user names: - Local file: a path such as `./drafts/column.md` @@ -165,7 +167,7 @@ Voice comes from stance and rhythm, never from invented biography. The most comm Where prose is flat because it lacks concrete detail, the fix is to ask the writer for the detail, or to flag the gap. It is not to supply one. Stance, reaction, uncertainty, and cadence are yours to add. Facts and history are not. -Skip this section entirely for clinical, legal, regulatory, and submitted academic material, where first-person texture is not the register and the added-voice failure mode is expensive. +Skip this section entirely for clinical, legal, regulatory, and submitted academic material, where first-person texture is not the register and the added-voice failure mode is expensive. Skip it even when a first-person corpus is available. Do not import memoir texture, "I", or invented biography into those registers. ### Before (clean but soulless) diff --git a/test/document-intake-policy.test.js b/test/document-intake-policy.test.js index e5686e5..395e4d7 100644 --- a/test/document-intake-policy.test.js +++ b/test/document-intake-policy.test.js @@ -11,6 +11,7 @@ import { parseCorpusPointer, resolveDeliveryMode, resolvePluginSource, + resolveVoicePrecedence, selectMaterialQuestion, } from '../scripts/lib/document-intake-policy.js'; @@ -392,3 +393,59 @@ test('asks for a plugin grant when a plugin is named without one', () => { assert.equal(decision.question.field, 'plugin_grant'); assert.match(decision.question.text, /plugin/i); }); + +test('a calibrated sample outranks the dash ban but not never-add or protected spans', () => { + const dash = resolveVoicePrecedence({ sampleUsesEmDash: true, styleRule: 'dash-ban' }); + assert.equal(dash.style_override, 'keep-sample-dash'); + + const blocked = resolveVoicePrecedence({ + sampleUsesEmDash: true, + styleRule: 'dash-ban', + corpusFact: 'I worked at Example Hospital in 2019.', + protectedSpan: true, + }); + assert.equal(blocked.style_override, 'keep-sample-dash'); + assert.equal(blocked.may_insert_corpus_fact, false); + assert.equal(blocked.insert_reason, 'protected-span'); + + const neverAdd = resolveVoicePrecedence({ + corpusFact: 'I worked at Example Hospital in 2019.', + neverAddConflict: true, + }); + assert.equal(neverAdd.may_insert_corpus_fact, false); + assert.equal(neverAdd.insert_reason, 'never-add'); +}); + +test('corpus facts cannot be inserted into the current draft unless asked and supported', () => { + const silent = resolveVoicePrecedence({ + corpusFact: 'The 2019 cohort used a stepped-wedge design.', + }); + assert.equal(silent.may_insert_corpus_fact, false); + assert.equal(silent.insert_reason, 'corpus-does-not-license-fabrication'); + + const askedOnly = resolveVoicePrecedence({ + corpusFact: 'The 2019 cohort used a stepped-wedge design.', + userAsked: true, + currentSourceSupports: false, + }); + assert.equal(askedOnly.may_insert_corpus_fact, false); + assert.equal(askedOnly.insert_reason, 'current-source-lacks-claim'); + + const allowed = resolveVoicePrecedence({ + corpusFact: 'The 2019 cohort used a stepped-wedge design.', + userAsked: true, + currentSourceSupports: true, + }); + assert.equal(allowed.may_insert_corpus_fact, true); + assert.equal(allowed.insert_reason, 'user-asked-and-source-supports'); +}); + +test('skips personality and first-person texture for restricted document classes', () => { + for (const documentClass of ['clinical', 'legal', 'regulatory', 'submitted-academic']) { + const decision = resolveVoicePrecedence({ documentClass }); + assert.equal(decision.skip_personality, true, documentClass); + assert.equal(decision.skip_first_person_texture, true, documentClass); + } + const ordinary = resolveVoicePrecedence({ documentClass: 'workplace' }); + assert.equal(ordinary.skip_personality, false); +}); diff --git a/test/voice-corpus-calibration.test.js b/test/voice-corpus-calibration.test.js index 231a478..b12b9b0 100644 --- a/test/voice-corpus-calibration.test.js +++ b/test/voice-corpus-calibration.test.js @@ -33,3 +33,21 @@ test('voice calibration keeps host plugins off until named and granted', () => { assert.match(source, /Default is off/); } }); + +test('skill prose says corpus facts do not license fabrication', () => { + for (const source of [canonicalSource, generated, coreReference]) { + assert.match(source, /does not license fabrication/); + assert.match(source, /you have written this before/); + assert.match(source, /current source already supports/); + } +}); + +test('personality skip covers clinical legal regulatory and submitted academic even with a corpus', () => { + for (const source of [canonicalSource, generated, coreReference]) { + assert.match( + source, + /Skip this section entirely for clinical, legal, regulatory, and submitted academic/ + ); + assert.match(source, /even when a first-person corpus is available/); + } +});