@@ -2515,6 +2515,9 @@ def __init__(self, project_root: Path):
25152515 self .extensions_dir = project_root / ".specify" / "extensions"
25162516 self .config_file = project_root / ".specify" / "extensions.yml"
25172517 self ._init_options_cache : Optional [Dict [str , Any ]] = None
2518+ self ._integration_invocation_cache : Optional [
2519+ tuple [Dict [str , Any ] | None , str | None , Any | None ]
2520+ ] = None
25182521
25192522 def _load_init_options (self ) -> Dict [str , Any ]:
25202523 """Load persisted init options used to determine invocation style.
@@ -2529,6 +2532,27 @@ def _load_init_options(self) -> Dict[str, Any]:
25292532 self ._init_options_cache = payload if isinstance (payload , dict ) else {}
25302533 return self ._init_options_cache
25312534
2535+ def _load_integration_invocation_context (
2536+ self ,
2537+ ) -> tuple [Dict [str , Any ] | None , str | None , Any | None ]:
2538+ """Load integration metadata used by hook rendering once per executor."""
2539+ if self ._integration_invocation_cache is None :
2540+ try :
2541+ from .integration_state import default_integration_key , try_read_integration_json
2542+ from .integrations import get_integration
2543+
2544+ state , _ = try_read_integration_json (self .project_root )
2545+ key = default_integration_key (state ) if state else None
2546+ integration = get_integration (key ) if key else None
2547+ self ._integration_invocation_cache = (state , key , integration )
2548+ except Exception :
2549+ # Hook rendering must keep working for projects with older or
2550+ # unreadable integration metadata; the init-options fallback
2551+ # below preserves the legacy behavior in that case.
2552+ self ._integration_invocation_cache = (None , None , None )
2553+
2554+ return self ._integration_invocation_cache
2555+
25322556 @staticmethod
25332557 def _skill_name_from_command (command : Any ) -> str :
25342558 """Map a command id like speckit.plan to speckit-plan skill name."""
@@ -2551,20 +2575,15 @@ def _render_hook_invocation(self, command: Any) -> str:
25512575 if command_id .startswith ("speckit." ):
25522576 try :
25532577 from .integration_runtime import user_command_invocation_for_integration
2554- from .integration_state import default_integration_key , try_read_integration_json
2555- from .integrations import get_integration
25562578
2557- state , _ = try_read_integration_json (self .project_root )
2558- if state :
2559- key = default_integration_key (state )
2560- integration = get_integration (key ) if key else None
2561- if integration :
2562- return user_command_invocation_for_integration (
2563- integration ,
2564- state ,
2565- key ,
2566- command_id ,
2567- )
2579+ state , key , integration = self ._load_integration_invocation_context ()
2580+ if state and key and integration :
2581+ return user_command_invocation_for_integration (
2582+ integration ,
2583+ state ,
2584+ key ,
2585+ command_id ,
2586+ )
25682587 except Exception :
25692588 # Hook rendering must keep working for projects with older or
25702589 # unreadable integration metadata; the init-options fallback
0 commit comments