MCPcopy Index your code
hub / github.com/github/spec-kit / ExtensionManager

Class ExtensionManager

src/specify_cli/extensions/__init__.py:689–1886  ·  view source on GitHub ↗

Manages extension lifecycle: installation, removal, updates.

Source from the content-addressed store, hash-verified

687
688
689class ExtensionManager:
690 """Manages extension lifecycle: installation, removal, updates."""
691
692 def __init__(self, project_root: Path):
693 """Initialize extension manager.
694
695 Args:
696 project_root: Path to project root directory
697 """
698 self.project_root = project_root
699 self.extensions_dir = project_root / ".specify" / "extensions"
700 self.registry = ExtensionRegistry(self.extensions_dir)
701
702 @staticmethod
703 def _collect_manifest_command_names(manifest: ExtensionManifest) -> Dict[str, str]:
704 """Collect command and alias names declared by a manifest.
705
706 Performs install-time validation for extension-specific constraints:
707 - primary commands must use the canonical `speckit.{extension}.{command}` shape
708 - primary commands must use this extension's namespace
709 - command namespaces must not shadow core commands
710 - duplicate command/alias names inside one manifest are rejected
711 - aliases are validated for type and uniqueness only (no pattern enforcement)
712
713 Args:
714 manifest: Parsed extension manifest
715
716 Returns:
717 Mapping of declared command/alias name -> kind ("command"/"alias")
718
719 Raises:
720 ValidationError: If any declared name is invalid
721 """
722 if manifest.id in CORE_COMMAND_NAMES:
723 raise ValidationError(
724 f"Extension ID '{manifest.id}' conflicts with core command namespace '{manifest.id}'"
725 )
726
727 declared_names: Dict[str, str] = {}
728
729 for cmd in manifest.commands:
730 primary_name = cmd["name"]
731 aliases = cmd.get("aliases", [])
732
733 if aliases is None:
734 aliases = []
735 if not isinstance(aliases, list):
736 raise ValidationError(
737 f"Aliases for command '{primary_name}' must be a list"
738 )
739
740 for kind, name in [("command", primary_name)] + [
741 ("alias", alias) for alias in aliases
742 ]:
743 if not isinstance(name, str):
744 raise ValidationError(
745 f"{kind.capitalize()} for command '{primary_name}' must be a string"
746 )

Calls

no outgoing calls