from pathlib import Path import tempfile import unittest from nightshift.errors import PipelineError from nightshift.patches import FileUpdate from nightshift.writing_validators import collect_writing_warnings, validate_writing_file_updates class WritingValidatorTests(unittest.TestCase): def test_rejects_character_pronoun_canon_changes(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) (root / "story").mkdir() (root / "story" / "characters.md").write_text( """# Characters ## Cricket ### Pronouns / Reference - Pronouns: she/her - Narrative reference: Cricket; she/her Scavenger. """, encoding="utf-8", ) updates = ( FileUpdate( path="story/characters.md", content="""# Characters ## Cricket ### Pronouns / Reference - Pronouns: they/them - Narrative reference: Cricket; they/them Scavenger. """, ), ) with self.assertRaisesRegex(PipelineError, "protected character pronoun canon changed"): validate_writing_file_updates(updates, root) def test_reports_scene_pronoun_drift_as_warning(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) (root / "story" / "chapters").mkdir(parents=True) (root / "story" / "characters.md").write_text( """# Characters ## Proxy ### Pronouns / Reference - Pronouns: she/her - Narrative reference: Proxy; she/her """, encoding="utf-8", ) updates = ( FileUpdate( path="story/chapters/chapter-001/scene-001.md", content="Proxy checked the rack. He shut down the bad job.\n", ), ) validate_writing_file_updates(updates, root) warnings = collect_writing_warnings(updates, root) self.assertEqual(len(warnings), 1) self.assertIn("Proxy", warnings[0]) self.assertIn("found `he`", warnings[0]) def test_allows_scene_pronouns_when_multiple_characters_make_ambiguous_sentence(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) (root / "story" / "chapters" / "chapter-001").mkdir(parents=True) (root / "story" / "characters.md").write_text( """# Characters ## Proxy ### Pronouns / Reference - Pronouns: she/her - Narrative reference: Proxy; she/her ## Saint ### Pronouns / Reference - Pronouns: he/him - Narrative reference: Saint; he/him """, encoding="utf-8", ) updates = ( FileUpdate( path="story/chapters/chapter-001/scene-001.md", content="Proxy watched Saint as he picked up the phone.\n", ), ) validate_writing_file_updates(updates, root) def test_allows_pronoun_before_other_character_reference(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) (root / "story" / "chapters" / "chapter-001").mkdir(parents=True) (root / "story" / "characters.md").write_text( """# Characters ## Proxy ### Pronouns / Reference - Pronouns: she/her - Narrative reference: Proxy; she/her ## DJ BLOODMONEY ### Pronouns / Reference - Pronouns: they/them or he/him - Narrative default: BLOODMONEY; they/them """, encoding="utf-8", ) updates = ( FileUpdate( path="story/chapters/chapter-001/scene-001.md", content=( "BLOODMONEY stood behind the turntables. " "He adjusted the EQ with one hand, let his hair fall into his eyes, " "and glanced over at Proxy without breaking the groove.\n" ), ), ) validate_writing_file_updates(updates, root) if __name__ == "__main__": unittest.main()