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

Function load_custom_steps

src/specify_cli/workflows/__init__.py:75–203  ·  view source on GitHub ↗

Load community-installed custom step types into STEP_REGISTRY. Scans ``.specify/workflows/steps/`` for installed step packages. Each valid package must contain ``step.yml`` (with a ``step.type_key`` field) and ``__init__.py`` (a ``StepBase`` subclass). Returns a list of type_keys t

(project_root: Path)

Source from the content-addressed store, hash-verified

73
74
75def load_custom_steps(project_root: Path) -> list[str]:
76 """Load community-installed custom step types into STEP_REGISTRY.
77
78 Scans ``.specify/workflows/steps/`` for installed step packages.
79 Each valid package must contain ``step.yml`` (with a ``step.type_key``
80 field) and ``__init__.py`` (a ``StepBase`` subclass).
81
82 Returns a list of type_keys that were successfully loaded.
83 Silently skips packages that fail to import or validate.
84 """
85 import hashlib as _hashlib
86 import importlib.util as _importlib_util
87 import re as _re
88 import sys as _sys
89
90 steps_dir = Path(project_root) / ".specify" / "workflows" / "steps"
91
92 # Defense-in-depth: refuse to execute step code from a symlinked
93 # parent directory under .specify/workflows/steps, which could redirect
94 # the import outside the project root and bypass the install-time
95 # symlink guard. Check symlinks *before* is_dir() since the latter
96 # follows symlinks and would stat an external target.
97 _current = Path(project_root)
98 for _part in (".specify", "workflows", "steps"):
99 _current = _current / _part
100 if _current.is_symlink():
101 return []
102
103 if not steps_dir.is_dir():
104 return []
105
106 loaded: list[str] = []
107 for step_dir in steps_dir.iterdir():
108 # Check symlinks before is_dir() since the latter follows symlinks
109 # and would stat an external target through a symlinked directory.
110 if step_dir.is_symlink():
111 continue
112 if not step_dir.is_dir():
113 continue
114 step_yml = step_dir / "step.yml"
115 init_py = step_dir / "__init__.py"
116 if step_yml.is_symlink() or init_py.is_symlink():
117 continue
118 if not step_yml.is_file() or not init_py.is_file():
119 continue
120
121 try:
122 import yaml as _yaml
123
124 meta = _yaml.safe_load(step_yml.read_text(encoding="utf-8")) or {}
125 step_meta = meta.get("step", {})
126 type_key = step_meta.get("type_key", "")
127 if not type_key:
128 continue
129
130 # Skip if already registered (e.g. built-in or previously loaded)
131 if type_key in STEP_REGISTRY:
132 continue

Calls 3

_register_stepFunction · 0.85
read_textMethod · 0.80
getMethod · 0.45