Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
215 lines
6.1 KiB
Python
Executable File
215 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Documentation Sync Runner
|
|
|
|
Execute documentation generators based on manifest configuration.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List, Dict, Any
|
|
import logging
|
|
|
|
# Setup logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s [%(levelname)s] %(message)s',
|
|
datefmt='%H:%M:%S'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DocSyncRunner:
|
|
"""
|
|
Documentation synchronization runner.
|
|
|
|
Reads manifest.json and executes configured generators.
|
|
"""
|
|
|
|
def __init__(self, project_root: Path):
|
|
"""
|
|
Initialize runner.
|
|
|
|
Args:
|
|
project_root: Project root directory
|
|
"""
|
|
self.project_root = Path(project_root)
|
|
self.manifest_path = self.project_root / ".dss" / "doc-sync" / "manifest.json"
|
|
self.manifest = self._load_manifest()
|
|
|
|
def _load_manifest(self) -> Dict[str, Any]:
|
|
"""
|
|
Load manifest.json configuration.
|
|
|
|
Returns:
|
|
Manifest dictionary
|
|
"""
|
|
if not self.manifest_path.exists():
|
|
logger.error(f"Manifest not found: {self.manifest_path}")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
with open(self.manifest_path, 'r') as f:
|
|
return json.load(f)
|
|
except Exception as e:
|
|
logger.error(f"Failed to load manifest: {e}")
|
|
sys.exit(1)
|
|
|
|
def run_generators(self, trigger: str = "manual") -> None:
|
|
"""
|
|
Run all generators configured for the given trigger.
|
|
|
|
Args:
|
|
trigger: Trigger type (post-commit, manual, etc.)
|
|
"""
|
|
logger.info(f"Running documentation sync (trigger: {trigger})")
|
|
|
|
code_mappings = self.manifest.get("sources", {}).get("code_mappings", [])
|
|
|
|
for mapping in code_mappings:
|
|
# Check if this generator should run for this trigger
|
|
if trigger not in mapping.get("triggers", ["manual"]):
|
|
logger.debug(f"Skipping {mapping['generator']} (trigger mismatch)")
|
|
continue
|
|
|
|
# Check if generator is enabled
|
|
generator_config = self.manifest.get("generators", {}).get(mapping["generator"], {})
|
|
if not generator_config.get("enabled", False):
|
|
logger.warning(f"Generator {mapping['generator']} is disabled, skipping")
|
|
continue
|
|
|
|
# Run generator
|
|
self._run_generator(mapping)
|
|
|
|
logger.info("Documentation sync complete")
|
|
|
|
def _run_generator(self, mapping: Dict[str, Any]) -> None:
|
|
"""
|
|
Run a specific generator.
|
|
|
|
Args:
|
|
mapping: Code mapping configuration
|
|
"""
|
|
generator_name = mapping["generator"]
|
|
source_pattern = mapping["pattern"]
|
|
target_path = self.project_root / mapping["extracts_to"]
|
|
|
|
logger.info(f"Running {generator_name}: {source_pattern} → {target_path}")
|
|
|
|
try:
|
|
# Import generator class
|
|
if generator_name == "api_extractor":
|
|
from generators.api_extractor import APIExtractor
|
|
generator = APIExtractor(self.project_root)
|
|
|
|
elif generator_name == "mcp_extractor":
|
|
from generators.mcp_extractor import MCPExtractor
|
|
generator = MCPExtractor(self.project_root)
|
|
|
|
else:
|
|
logger.warning(f"Unknown generator: {generator_name}")
|
|
return
|
|
|
|
# Resolve source path (handle patterns)
|
|
source_paths = self._resolve_source_paths(source_pattern)
|
|
|
|
if not source_paths:
|
|
logger.warning(f"No source files found for pattern: {source_pattern}")
|
|
return
|
|
|
|
# Run generator for first matching file (extend later for multiple)
|
|
source_path = source_paths[0]
|
|
generator.run(source_path, target_path)
|
|
|
|
logger.info(f"✓ Generated: {target_path}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to run {generator_name}: {e}", exc_info=True)
|
|
|
|
def _resolve_source_paths(self, pattern: str) -> List[Path]:
|
|
"""
|
|
Resolve source file paths from pattern.
|
|
|
|
Args:
|
|
pattern: File pattern (e.g., "tools/api/server.py")
|
|
|
|
Returns:
|
|
List of matching paths
|
|
"""
|
|
# Simple implementation: exact match only
|
|
# TODO: Add glob pattern support
|
|
|
|
source_path = self.project_root / pattern
|
|
|
|
if source_path.exists():
|
|
return [source_path]
|
|
|
|
return []
|
|
|
|
def validate_manifest(self) -> bool:
|
|
"""
|
|
Validate manifest syntax and configuration.
|
|
|
|
Returns:
|
|
True if valid, False otherwise
|
|
"""
|
|
logger.info("Validating manifest.json")
|
|
|
|
# Check required sections
|
|
required_sections = ["sources", "generators", "git_hooks"]
|
|
for section in required_sections:
|
|
if section not in self.manifest:
|
|
logger.error(f"Missing required section: {section}")
|
|
return False
|
|
|
|
# Check code mappings
|
|
code_mappings = self.manifest.get("sources", {}).get("code_mappings", [])
|
|
if not code_mappings:
|
|
logger.error("No code mappings defined")
|
|
return False
|
|
|
|
logger.info("✓ Manifest is valid")
|
|
return True
|
|
|
|
|
|
def main():
|
|
"""
|
|
Main entry point for CLI usage.
|
|
"""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="Documentation Sync Runner")
|
|
parser.add_argument(
|
|
"command",
|
|
choices=["run", "validate"],
|
|
help="Command to execute"
|
|
)
|
|
parser.add_argument(
|
|
"--trigger",
|
|
default="manual",
|
|
help="Trigger type (post-commit, manual, etc.)"
|
|
)
|
|
parser.add_argument(
|
|
"--project-root",
|
|
type=Path,
|
|
default=Path(__file__).parent.parent.parent,
|
|
help="Project root directory"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
runner = DocSyncRunner(args.project_root)
|
|
|
|
if args.command == "validate":
|
|
success = runner.validate_manifest()
|
|
sys.exit(0 if success else 1)
|
|
|
|
elif args.command == "run":
|
|
runner.run_generators(trigger=args.trigger)
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|