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

Class StepCatalog

src/specify_cli/workflows/catalog.py:737–1199  ·  view source on GitHub ↗

Manages step catalog fetching, caching, and searching. Resolution order for catalog sources: 1. ``SPECKIT_STEP_CATALOG_URL`` env var (overrides all) 2. Project-level ``.specify/step-catalogs.yml`` 3. User-level ``~/.specify/step-catalogs.yml`` 4. Built-in defaults (official + co

Source from the content-addressed store, hash-verified

735
736
737class StepCatalog:
738 """Manages step catalog fetching, caching, and searching.
739
740 Resolution order for catalog sources:
741 1. ``SPECKIT_STEP_CATALOG_URL`` env var (overrides all)
742 2. Project-level ``.specify/step-catalogs.yml``
743 3. User-level ``~/.specify/step-catalogs.yml``
744 4. Built-in defaults (official + community)
745 """
746
747 DEFAULT_CATALOG_URL = (
748 "https://raw.githubusercontent.com/github/spec-kit/main/"
749 "workflows/step-catalog.json"
750 )
751 COMMUNITY_CATALOG_URL = (
752 "https://raw.githubusercontent.com/github/spec-kit/main/"
753 "workflows/step-catalog.community.json"
754 )
755 CACHE_DURATION = 3600 # 1 hour
756
757 def __init__(self, project_root: Path) -> None:
758 self.project_root = project_root
759 self.steps_dir = project_root / ".specify" / "workflows" / "steps"
760 self.cache_dir = self.steps_dir / ".cache"
761
762 def _is_cache_path_safe(self) -> bool:
763 """Return False if any component of the cache path is a symlink."""
764 current = self.project_root
765 for part in (".specify", "workflows", "steps", ".cache"):
766 current = current / part
767 if current.is_symlink():
768 return False
769 return True
770
771 # -- Catalog resolution -----------------------------------------------
772
773 def _validate_catalog_url(self, url: str) -> None:
774 """Validate that a catalog URL uses HTTPS (localhost HTTP allowed)."""
775 from urllib.parse import urlparse
776
777 parsed = urlparse(url)
778 is_localhost = parsed.hostname in ("localhost", "127.0.0.1", "::1")
779 if parsed.scheme != "https" and not (
780 parsed.scheme == "http" and is_localhost
781 ):
782 raise StepValidationError(
783 f"Catalog URL must use HTTPS (got {parsed.scheme}://). "
784 "HTTP is only allowed for localhost."
785 )
786 if not parsed.hostname:
787 raise StepValidationError(
788 "Catalog URL must be a valid URL with a host."
789 )
790
791 def _load_catalog_config(
792 self, config_path: Path
793 ) -> list[StepCatalogEntry] | None:
794 """Load catalog stack configuration from a YAML file."""

Calls

no outgoing calls