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,28 +838,85 @@ 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 ():
859+ active_skills_output = (
860+ agent_name == active_skills_agent
861+ and agent_config .get ("extension" ) == "/SKILL.md"
862+ )
863+ recovered_active_skills_dir : Optional [Path ] = None
828864 # Check detect_dir first (project-local marker) if configured,
829865 # falling back to the resolved dir for output. This prevents
830866 # global dirs (e.g. ~/.hermes/skills) from causing false
831867 # detection in every project.
832868 detect_dir_str = agent_config .get ("detect_dir" )
833869 if detect_dir_str :
834870 detect_path = project_root / detect_dir_str
835- if not detect_path .exists ():
836- continue
871+ if not detect_path .is_dir ():
872+ if not active_skills_output :
873+ continue
874+ try :
875+ from . import resolve_active_skills_dir
876+
877+ recovered_active_skills_dir = (
878+ resolve_active_skills_dir (project_root )
879+ )
880+ except (ValueError , OSError ):
881+ continue
882+ if recovered_active_skills_dir is None or not detect_path .is_dir ():
883+ continue
884+ active_created_skills_dir = recovered_active_skills_dir
837885 agent_dir = self ._resolve_agent_dir (
838886 agent_name , agent_config , project_root ,
839887 )
840888
841- if agent_dir .exists ():
889+ agent_dir_existed = agent_dir .is_dir ()
890+ register_missing_active_skills_agent = (
891+ not agent_dir_existed
892+ and active_skills_output
893+ )
894+ if register_missing_active_skills_agent :
895+ if recovered_active_skills_dir is None :
896+ try :
897+ from . import resolve_active_skills_dir
898+
899+ recovered_active_skills_dir = (
900+ resolve_active_skills_dir (project_root )
901+ )
902+ except (ValueError , OSError ):
903+ continue
904+ if recovered_active_skills_dir is None :
905+ continue
906+ active_created_skills_dir = recovered_active_skills_dir
907+ # Shared skill dirs such as .agents/skills should not make
908+ # later integrations look detected when the active agent just
909+ # recreated the directory during this registration pass.
910+ created_by_active_agent = (
911+ active_created_skills_dir is not None
912+ and self ._same_lexical_path (agent_dir , active_created_skills_dir )
913+ and agent_name != active_skills_agent
914+ )
915+ should_register = (
916+ agent_dir_existed and not created_by_active_agent
917+ ) or register_missing_active_skills_agent
918+
919+ if should_register :
842920 try :
843921 registered = self .register_commands (
844922 agent_name ,
@@ -852,8 +930,16 @@ def register_commands_for_all_agents(
852930 )
853931 if registered :
854932 results [agent_name ] = registered
933+ if register_missing_active_skills_agent :
934+ active_created_skills_dir = (
935+ recovered_active_skills_dir or agent_dir
936+ )
855937 except ValueError :
856938 continue
939+ except OSError :
940+ if register_missing_active_skills_agent :
941+ continue
942+ raise
857943
858944 return results
859945
@@ -892,12 +978,12 @@ def register_commands_for_non_skill_agents(
892978 detect_dir_str = agent_config .get ("detect_dir" )
893979 if detect_dir_str :
894980 detect_path = project_root / detect_dir_str
895- if not detect_path .exists ():
981+ if not detect_path .is_dir ():
896982 continue
897983 agent_dir = self ._resolve_agent_dir (
898984 agent_name , agent_config , project_root ,
899985 )
900- if agent_dir .exists ():
986+ if agent_dir .is_dir ():
901987 try :
902988 registered = self .register_commands (
903989 agent_name ,
0 commit comments