#!/usr/bin/env python3 """ Design Diversification Tracker Tracks past design outputs to prevent structural repetition. Inspired by Hallmark's .hallmark/log.json system. Usage: python3 design-log.py add --macrostructure "bento-grid" --theme "quiet" --brief "SaaS landing page" python3 design-log.py recent --count 5 python3 design-log.py check --macrostructure "bento-grid" --theme "quiet" python3 design-log.py reset """ import json import os from pathlib import Path from dataclasses import dataclass, asdict from datetime import datetime from typing import Optional DESIGN_LOG_DIR = Path.home() / '.claude' / 'skills' / 'design' DESIGN_LOG_FILE = DESIGN_LOG_DIR / 'design-log.json' @dataclass class DesignEntry: """A single design output entry.""" date: str macrostructure: str theme: str genre: str brief: str components: list = None slug: Optional[str] = None def __post_init__(self): if self.components is None: self.components = [] class DesignLog: """Manages the design diversification log.""" def __init__(self, log_file: Path = None): self.log_file = log_file or DESIGN_LOG_FILE self.log_file.parent.mkdir(parents=True, exist_ok=True) def _load(self) -> list: """Load entries from log file.""" if not self.log_file.exists(): return [] try: with open(self.log_file) as f: return json.load(f) except json.JSONDecodeError: return [] def _save(self, entries: list): """Save entries to log file.""" with open(self.log_file, 'w') as f: json.dump(entries, f, indent=2) def add(self, macrostructure: str, theme: str = None, genre: str = None, brief: str = None, components: list = None, slug: str = None): """Add a new design entry.""" entries = self._load() entry = DesignEntry( date=datetime.now().isoformat()[:10], macrostructure=macrostructure, theme=theme or 'none', genre=genre or 'unknown', brief=brief or '', components=components or [], slug=slug ) entries.insert(0, asdict(entry)) # Newest first self._save(entries) print(f"Added entry: {macrostructure} ({theme or 'no theme'})") return entry def recent(self, count: int = 5) -> list: """Get the N most recent entries.""" entries = self._load() return [DesignEntry(**e) for e in entries[:count]] def check(self, macrostructure: str = None, theme: str = None, genre: str = None, components: list = None) -> dict: """Check if proposed design conflicts with recent outputs.""" entries = self._load() warnings = [] suggestions = [] # Check macrostructure (last 3) if macrostructure: recent_structs = [e['macrostructure'] for e in entries[:3]] if macrostructure.lower() in [s.lower() for s in recent_structs]: warnings.append(f"Macrostructure '{macrostructure}' used in last 3 outputs") # Suggest alternatives all_structs = { 'bento-grid': ['manifesto', 'long-document', 'stat-led'], 'manifesto': ['bento-grid', 'split-studio', 'photographic'], 'long-document': ['index-first', 'letter', 'bento-grid'], 'stat-led': ['bento-grid', 'workbench', 'quote-led'], 'workbench': ['stat-led', 'bento-grid', 'long-document'], 'photographic': ['manifesto', 'split-studio', 'bento-grid'], 'quote-led': ['stat-led', 'bento-grid', 'letter'], 'split-studio': ['photographic', 'bento-grid', 'index-first'], 'index-first': ['long-document', 'letter', 'bento-grid'], 'letter': ['long-document', 'index-first', 'manifesto'], } suggested = all_structs.get(macrostructure.lower(), ['bento-grid', 'long-document', 'manifesto']) suggestions.append(f"Suggested alternatives: {', '.join(suggested[:3])}") # Check theme (last 3) if theme: recent_themes = [e.get('theme', '') for e in entries[:3]] if theme.lower() in [t.lower() for t in recent_themes if t]: warnings.append(f"Theme '{theme}' used in last 3 outputs") # Check genre (last 3) if genre: recent_genres = [e.get('genre', '') for e in entries[:3]] if genre.lower() in [g.lower() for g in recent_genres if g]: warnings.append(f"Genre '{genre}' used in last 3 outputs") # Check components if components: for component in components: recent_components = [] for entry in entries[:3]: recent_components.extend(entry.get('components', [])) if component in recent_components: warnings.append(f"Component '{component}' used in recent output") return { 'conflicts': len(warnings) > 0, 'warnings': warnings, 'suggestions': suggestions, 'recent': entries[:3] } def format_recent(self, count: int = 5) -> str: """Format recent entries as readable markdown.""" recent = self.recent(count) if not recent: return "## Design History\n\n*No design outputs logged yet.*\n" lines = ["## Design History (Recent)\n"] for i, entry in enumerate(recent): date = entry.date macro = entry.macrostructure theme = entry.theme or '—' genre = entry.genre or '—' brief = entry.brief[:50] + "..." if entry.brief and len(entry.brief) > 50 else entry.brief or '—' lines.append(f"{i+1}. **{date}** — {macro}") lines.append(f" - Theme: {theme} | Genre: {genre}") if brief and brief != '—': lines.append(f" - Brief: {brief}") return "\n".join(lines) def reset(self): """Clear all entries.""" self._save([]) print("Design log cleared.") def main(): import argparse parser = argparse.ArgumentParser(description='Design diversification tracker') subparsers = parser.add_subparsers(dest='command', help='Commands') # add command add_parser = subparsers.add_parser('add', help='Add a design entry') add_parser.add_argument('--macrostructure', required=True) add_parser.add_argument('--theme') add_parser.add_argument('--genre') add_parser.add_argument('--brief') add_parser.add_argument('--components', nargs='+') add_parser.add_argument('--slug') # recent command recent_parser = subparsers.add_parser('recent', help='Show recent entries') recent_parser.add_argument('--count', type=int, default=5) # check command check_parser = subparsers.add_parser('check', help='Check for conflicts') check_parser.add_argument('--macrostructure') check_parser.add_argument('--theme') check_parser.add_argument('--genre') check_parser.add_argument('--components', nargs='+') # reset command subparsers.add_parser('reset', help='Clear log') args = parser.parse_args() log = DesignLog() if args.command == 'add': log.add( macrostructure=args.macrostructure, theme=args.theme, genre=args.genre, brief=args.brief, components=args.components, slug=args.slug ) elif args.command == 'recent': print(log.format_recent(args.count)) elif args.command == 'check': result = log.check( macrostructure=args.macrostructure, theme=args.theme, genre=args.genre, components=args.components ) if result['conflicts']: print("⚠️ Conflicts detected:") for warning in result['warnings']: print(f" - {warning}") for suggestion in result['suggestions']: print(f" 💡 {suggestion}") else: print("✅ No conflicts detected.") elif args.command == 'reset': log.reset() else: parser.print_help() if __name__ == '__main__': main()