1515
1616import yaml
1717
18+ from ._init_options import is_ai_skills_enabled , load_init_options
19+
1820
1921def _build_agent_configs () -> dict [str , Any ]:
2022 """Derive CommandRegistrar.AGENT_CONFIGS from INTEGRATION_REGISTRY."""
@@ -359,11 +361,6 @@ def resolve_skill_placeholders(
359361 agent_name : str , frontmatter : dict , body : str , project_root : Path
360362 ) -> str :
361363 """Resolve script placeholders for skills-backed agents."""
362- try :
363- from . import load_init_options
364- except ImportError :
365- return body
366-
367364 if not isinstance (frontmatter , dict ):
368365 frontmatter = {}
369366
@@ -474,6 +471,29 @@ def _is_safe_command_name(name: str) -> bool:
474471 return False
475472 return os .path .normpath (name ) == name
476473
474+ @staticmethod
475+ def _same_lexical_path (left : Path , right : Path ) -> bool :
476+ """Compare paths after lexical normalization without resolving symlinks."""
477+ return os .path .normcase (os .path .normpath (os .fspath (left ))) == os .path .normcase (
478+ os .path .normpath (os .fspath (right ))
479+ )
480+
481+ @staticmethod
482+ def _active_skills_agent (project_root : Path ) -> Optional [str ]:
483+ """Return the initialized skills-backed agent, if skills mode is active."""
484+ opts = load_init_options (project_root )
485+ if not isinstance (opts , dict ):
486+ return None
487+
488+ agent = opts .get ("ai" )
489+ if not isinstance (agent , str ) or not agent :
490+ return None
491+ # Kimi is a native skills integration; when ai_skills is not boolean
492+ # True, Kimi still uses its existing SKILL.md layout.
493+ if not is_ai_skills_enabled (opts ) and agent != "kimi" :
494+ return None
495+ return agent
496+
477497 def register_commands (
478498 self ,
479499 agent_name : str ,
@@ -806,6 +826,7 @@ def register_commands_for_all_agents(
806826 project_root : Path ,
807827 context_note : str = None ,
808828 link_outputs : bool = False ,
829+ create_missing_active_skills_dir : bool = False ,
809830 ) -> Dict [str , List [str ]]:
810831 """Register commands for all detected agents in the project.
811832
@@ -817,13 +838,23 @@ def register_commands_for_all_agents(
817838 context_note: Custom context comment for markdown output
818839 link_outputs: If True, create dev-mode symlinks for rendered
819840 command files when supported by the OS.
841+ create_missing_active_skills_dir: If True, attempt missing-dir
842+ recovery only for the active initialized skills-backed agent.
843+ Recovery requires active skills mode (or Kimi's existing native
844+ skills directory) and is skipped when safe resolution or
845+ creation fails.
820846
821847 Returns:
822848 Dictionary mapping agent names to list of registered commands
823849 """
824850 results = {}
825851
826852 self ._ensure_configs ()
853+ active_skills_agent = (
854+ self ._active_skills_agent (project_root )
855+ if create_missing_active_skills_dir else None
856+ )
857+ active_created_skills_dir : Optional [Path ] = None
827858 for agent_name , agent_config in self .AGENT_CONFIGS .items ():
828859 # Check detect_dir first (project-local marker) if configured,
829860 # falling back to the resolved dir for output. This prevents
@@ -838,7 +869,36 @@ def register_commands_for_all_agents(
838869 agent_name , agent_config , project_root ,
839870 )
840871
841- if agent_dir .exists ():
872+ agent_dir_existed = agent_dir .is_dir ()
873+ register_missing_active_skills_agent = (
874+ not agent_dir_existed
875+ and agent_name == active_skills_agent
876+ and agent_config .get ("extension" ) == "/SKILL.md"
877+ )
878+ if register_missing_active_skills_agent :
879+ try :
880+ from . import resolve_active_skills_dir
881+
882+ resolved_active_dir = resolve_active_skills_dir (project_root )
883+ except (ValueError , OSError ):
884+ continue
885+ if resolved_active_dir is None :
886+ continue
887+ agent_dir = resolved_active_dir
888+ active_created_skills_dir = agent_dir
889+ # Shared skill dirs such as .agents/skills should not make
890+ # later integrations look detected when the active agent just
891+ # recreated the directory during this registration pass.
892+ created_by_active_agent = (
893+ active_created_skills_dir is not None
894+ and self ._same_lexical_path (agent_dir , active_created_skills_dir )
895+ and agent_name != active_skills_agent
896+ )
897+ should_register = (
898+ agent_dir_existed and not created_by_active_agent
899+ ) or register_missing_active_skills_agent
900+
901+ if should_register :
842902 try :
843903 registered = self .register_commands (
844904 agent_name ,
@@ -852,8 +912,14 @@ def register_commands_for_all_agents(
852912 )
853913 if registered :
854914 results [agent_name ] = registered
915+ if register_missing_active_skills_agent :
916+ active_created_skills_dir = agent_dir
855917 except ValueError :
856918 continue
919+ except OSError :
920+ if register_missing_active_skills_agent :
921+ continue
922+ raise
857923
858924 return results
859925
@@ -897,7 +963,7 @@ def register_commands_for_non_skill_agents(
897963 agent_dir = self ._resolve_agent_dir (
898964 agent_name , agent_config , project_root ,
899965 )
900- if agent_dir .exists ():
966+ if agent_dir .is_dir ():
901967 try :
902968 registered = self .register_commands (
903969 agent_name ,
0 commit comments