feat: add config→style guide cross-reference and alignment tests (#472)

* feat: add config→style guide cross-reference and alignment tests - Add editorial_style_guide field to config/podcast.json pointing to docs/editorial-style-guide.md for traceability - Add TestPodcastConfigAlignedWithGuide test class validating that podcast.json segments, AI disclosure, and word count targets match the editorial style guide requirements Closes #337 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address review comments Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Juan Manuel Servera committed Jun 14, 2026 at 10:23 UTC 30fa83b4c8c40767657de1cd00f41528f65ed0c7
2 files changed +40
config/podcast.json
+1
@@ -1,4 +1,5 @@
1 {
2 + "editorial_style_guide": "docs/editorial-style-guide.md",
3 "podcast_config": {
4 "name": "Claracle",
5 "url": "https://www.claracle.com",
tests/test_editorial_style_guide.py
+39
@@ -5,11 +5,13 @@ safety boundaries, disclosure requirements, and structural constraints that
5 prevent unsafe content generation.
6 """
7
8 +import re
9 from pathlib import Path
10
11 import pytest
12
13 GUIDE_PATH = Path(__file__).resolve().parent.parent / "docs" / "editorial-style-guide.md"
14 +SEGMENT_TABLE_PATTERN = re.compile(r"^\|\s*\d+\s*\|\s*\*\*(?P<segment>[^*]+)\*\*\s*\|", re.MULTILINE)
15
16
17 @pytest.fixture
@@ -100,3 +102,40 @@ class TestEditorialStyleGuideSafety:
102 def test_sponsorship_guardrails(self, guide_content: str):
103 assert "FTC" in guide_content or "sponsorship" in guide_content.lower()
104 assert "brought to you by" in guide_content.lower()
105 +
106 +
107 +class TestPodcastConfigAlignedWithGuide:
108 + """Verify config/podcast.json aligns with the editorial style guide."""
109 +
110 + @pytest.fixture
111 + def podcast_config(self) -> dict:
112 + import json
113 + config_path = Path(__file__).resolve().parent.parent / "config" / "podcast.json"
114 + assert config_path.exists(), "config/podcast.json not found"
115 + return json.loads(config_path.read_text(encoding="utf-8"))
116 +
117 + def test_config_references_style_guide(self, podcast_config: dict):
118 + assert "editorial_style_guide" in podcast_config
119 + guide_path = Path(__file__).resolve().parent.parent / podcast_config["editorial_style_guide"]
120 + assert guide_path.exists(), f"Style guide path {podcast_config['editorial_style_guide']} does not exist"
121 +
122 + def test_segment_order_matches_guide(self, podcast_config: dict, guide_content: str):
123 + config_segments = podcast_config["script_directions"]["episode_style"]["segment_order"]
124 + guide_segments = [match.strip() for match in SEGMENT_TABLE_PATTERN.findall(guide_content)]
125 +
126 + assert guide_segments, "Could not extract locked segment order from style guide"
127 + assert len(guide_segments) == len(set(guide_segments)), "Style guide contains duplicate segments"
128 + assert config_segments == guide_segments, (
129 + "Podcast config segment_order must exactly match the style guide's locked segment order "
130 + f"(guide={guide_segments}, config={config_segments})"
131 + )
132 +
133 + def test_ai_disclosure_in_config(self, podcast_config: dict):
134 + opening = podcast_config["script_directions"]["opening_cues"]
135 + assert "ai_disclosure" in opening
136 + assert "first 60 seconds" in opening["ai_disclosure"].lower()
137 +
138 + def test_word_count_target_in_config(self, podcast_config: dict):
139 + fmt = podcast_config["script_directions"]["episode_style"]["format"]
140 + assert "1200" in fmt or "1,200" in fmt
141 + assert "1700" in fmt or "1,700" in fmt