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