"""Style reference loader for Content Pipeline v2. Loads JSON style examples from shared/project-docs/blog/style-examples/. These examples teach the LLM the target voice and structure. """ import json import os from pathlib import Path from typing import Optional STYLE_EXAMPLES_DIR = Path("/home/hoffmann_admin/.openclaw/shared/project-docs/blog/style-examples") def load_style_example(slug: str) -> Optional[dict]: """Load a style example by slug. Looks for {slug}.json in the style-examples directory. Returns None if not found. """ path = STYLE_EXAMPLES_DIR / f"{slug}.json" if not path.exists(): return None try: with open(path, "r", encoding="utf-8") as f: return json.load(f) except (json.JSONDecodeError, IOError): return None def list_style_examples() -> list[dict]: """List all available style examples. Returns list of {slug, title, excerpt} dicts. """ if not STYLE_EXAMPLES_DIR.exists(): return [] examples = [] for path in sorted(STYLE_EXAMPLES_DIR.glob("*.json")): try: with open(path, "r", encoding="utf-8") as f: data = json.load(f) examples.append({ "slug": data.get("slug", path.stem), "title": data.get("title", path.stem), "excerpt": data.get("excerpt", "")[:200], }) except (json.JSONDecodeError, IOError): continue return examples def build_style_prompt(style_reference: Optional[str]) -> str: """Build a style injection prompt from a style reference. If style_reference is None or not found, returns a generic prompt. """ if not style_reference: return _default_style_prompt() example = load_style_example(style_reference) if not example: return _default_style_prompt() # Extract voice markers voice = example.get("voice_markers", {}) structure = example.get("structure", {}) prompt_parts = [ "## Voice and Style Guide (from reference: {title})", "", "Write in first person. Use 'I' statements throughout.", "Include specific timestamps and temporal markers (2 AM, yesterday, etc.)", "Describe struggle honestly — what broke, what you tried, why it failed.", "Mention the real cost: sleep lost, family disrupted, frustration felt.", "End with reflection — what you learned, what you'd do differently.", "", "## Structural Template", "", ] if "hook" in structure: prompt_parts.append(f"1. Hook: {structure['hook'].get('type', 'timestamp_location_crisis')}") if "struggle" in structure: s = structure["struggle"] prompt_parts.append(f"2. Struggle: {s.get('attempts', 2)} failed attempts, escalating tension") if "moment" in structure: prompt_parts.append(f"3. The Moment: {structure['moment'].get('realization', 'The realization')}") if "fix" in structure: f = structure["fix"] prompt_parts.append(f"4. The Fix: {f.get('solution', 'The solution')} (note caveats: {f.get('caveats', 'none')})") if "reflection" in structure: prompt_parts.append(f"5. Reflection: {structure['reflection'].get('lesson', 'The lesson learned')}") prompt_parts.extend([ "", "## Voice Markers to Emulate", "", f"- I-statements: ~{voice.get('i_statements', 8)} per article", f"- Direct quotes: {', '.join(voice.get('quotes', ['Aundrea said...']))}", f"- Cost mentions: {', '.join(voice.get('cost_mentions', ['sleep', 'time']))}", "", ]) return "\n".join(prompt_parts).format(title=example.get("title", "Reference")) def _default_style_prompt() -> str: """Default style prompt when no reference is available.""" return """## Voice and Style Guide Write in first person. Use 'I' statements throughout. Include specific timestamps and temporal markers. Describe struggle honestly — what broke, what you tried, why it failed. Mention the real cost: sleep lost, family disrupted, frustration felt. End with reflection — what you learned, what you'd do differently. ## Structural Template 1. Hook: Start with a specific moment of crisis (timestamp + location) 2. Struggle: 2-3 failed attempts, escalating tension 3. The Moment: The realization or breakthrough 4. The Fix: The solution, with honest caveats 5. Reflection: What you learned, what you'd do differently """ def ensure_style_examples_dir(): """Ensure the style examples directory exists.""" STYLE_EXAMPLES_DIR.mkdir(parents=True, exist_ok=True) # Initialize on import ensure_style_examples_dir()