@@ -823,6 +823,7 @@ def _register_extension_skills(
823823 self ,
824824 manifest : ExtensionManifest ,
825825 extension_dir : Path ,
826+ link_outputs : bool = False ,
826827 ) -> List [str ]:
827828 """Generate SKILL.md files for extension commands as agent skills.
828829
@@ -834,6 +835,8 @@ def _register_extension_skills(
834835 Args:
835836 manifest: Extension manifest.
836837 extension_dir: Installed extension directory.
838+ link_outputs: If True, create dev-mode symlinks for rendered
839+ skill files when supported by the OS.
837840
838841 Returns:
839842 List of skill names that were created (for registry storage).
@@ -886,9 +889,18 @@ def _register_extension_skills(
886889 # Check if skill already exists before creating the directory
887890 skill_subdir = skills_dir / skill_name
888891 skill_file = skill_subdir / "SKILL.md"
889- if skill_file .exists ():
890- # Do not overwrite user-customized skills
891- continue
892+ cache_root = extension_dir / ".specify-dev" / "extension-skills"
893+ cache_file = cache_root / skill_name / "SKILL.md"
894+ CommandRegistrar ._ensure_inside (cache_file , cache_root )
895+ if skill_file .exists () or skill_file .is_symlink ():
896+ # Do not overwrite user-customized skills, but allow dev-mode
897+ # symlinks that point back to this extension's generated cache
898+ # to be refreshed on a subsequent dev install.
899+ if not (
900+ link_outputs
901+ and self ._is_expected_dev_symlink (skill_file , cache_file )
902+ ):
903+ continue
892904
893905 # Create skill directory; track whether we created it so we can clean
894906 # up safely if reading the source file subsequently fails.
@@ -940,11 +952,35 @@ def _register_extension_skills(
940952 skill_content
941953 )
942954
943- skill_file .write_text (skill_content , encoding = "utf-8" )
955+ if link_outputs :
956+ try :
957+ cache_file .parent .mkdir (parents = True , exist_ok = True )
958+ cache_file .write_text (skill_content , encoding = "utf-8" )
959+ if skill_file .exists () or skill_file .is_symlink ():
960+ skill_file .unlink ()
961+ target = os .path .relpath (cache_file , skill_file .parent )
962+ os .symlink (target , skill_file )
963+ except (OSError , ValueError ):
964+ if skill_file .is_symlink ():
965+ skill_file .unlink ()
966+ skill_file .write_text (skill_content , encoding = "utf-8" )
967+ else :
968+ skill_file .write_text (skill_content , encoding = "utf-8" )
944969 written .append (skill_name )
945970
946971 return written
947972
973+ @staticmethod
974+ def _is_expected_dev_symlink (skill_file : Path , cache_file : Path ) -> bool :
975+ """Return True when an existing skill file links to its dev cache."""
976+ if not skill_file .is_symlink ():
977+ return False
978+
979+ try :
980+ return skill_file .resolve (strict = False ) == cache_file .resolve (strict = False )
981+ except OSError :
982+ return False
983+
948984 def _unregister_extension_skills (
949985 self ,
950986 skill_names : List [str ],
@@ -1115,6 +1151,7 @@ def install_from_directory(
11151151 speckit_version : str ,
11161152 register_commands : bool = True ,
11171153 priority : int = 10 ,
1154+ link_commands : bool = False ,
11181155 ) -> ExtensionManifest :
11191156 """Install extension from a local directory.
11201157
@@ -1123,6 +1160,8 @@ def install_from_directory(
11231160 speckit_version: Current spec-kit version
11241161 register_commands: If True, register commands with AI agents
11251162 priority: Resolution priority (lower = higher precedence, default 10)
1163+ link_commands: If True, register rendered agent artifacts as
1164+ symlinks to a dev cache when supported by the OS.
11261165
11271166 Returns:
11281167 Installed extension manifest
@@ -1166,12 +1205,14 @@ def install_from_directory(
11661205 registrar = CommandRegistrar ()
11671206 # Register for all detected agents
11681207 registered_commands = registrar .register_commands_for_all_agents (
1169- manifest , dest_dir , self .project_root
1208+ manifest , dest_dir , self .project_root , link_outputs = link_commands
11701209 )
11711210
11721211 # Auto-register extension commands as agent skills when --ai-skills
11731212 # was used during project initialisation (feature parity).
1174- registered_skills = self ._register_extension_skills (manifest , dest_dir )
1213+ registered_skills = self ._register_extension_skills (
1214+ manifest , dest_dir , link_outputs = link_commands
1215+ )
11751216
11761217 # Register hooks and update installed list in extensions.yml
11771218 hook_executor = HookExecutor (self .project_root )
@@ -1607,28 +1648,32 @@ def register_commands_for_agent(
16071648 agent_name : str ,
16081649 manifest : ExtensionManifest ,
16091650 extension_dir : Path ,
1610- project_root : Path
1651+ project_root : Path ,
1652+ link_outputs : bool = False ,
16111653 ) -> List [str ]:
16121654 """Register extension commands for a specific agent."""
16131655 if agent_name not in self .AGENT_CONFIGS :
16141656 raise ExtensionError (f"Unsupported agent: { agent_name } " )
16151657 context_note = f"\n <!-- Extension: { manifest .id } -->\n <!-- Config: .specify/extensions/{ manifest .id } / -->\n "
16161658 return self ._registrar .register_commands (
16171659 agent_name , manifest .commands , manifest .id , extension_dir , project_root ,
1618- context_note = context_note
1660+ context_note = context_note ,
1661+ link_outputs = link_outputs ,
16191662 )
16201663
16211664 def register_commands_for_all_agents (
16221665 self ,
16231666 manifest : ExtensionManifest ,
16241667 extension_dir : Path ,
1625- project_root : Path
1668+ project_root : Path ,
1669+ link_outputs : bool = False ,
16261670 ) -> Dict [str , List [str ]]:
16271671 """Register extension commands for all detected agents."""
16281672 context_note = f"\n <!-- Extension: { manifest .id } -->\n <!-- Config: .specify/extensions/{ manifest .id } / -->\n "
16291673 return self ._registrar .register_commands_for_all_agents (
16301674 manifest .commands , manifest .id , extension_dir , project_root ,
1631- context_note = context_note
1675+ context_note = context_note ,
1676+ link_outputs = link_outputs ,
16321677 )
16331678
16341679 def unregister_commands (
@@ -1643,10 +1688,13 @@ def register_commands_for_claude(
16431688 self ,
16441689 manifest : ExtensionManifest ,
16451690 extension_dir : Path ,
1646- project_root : Path
1691+ project_root : Path ,
1692+ link_outputs : bool = False ,
16471693 ) -> List [str ]:
16481694 """Register extension commands for Claude Code agent."""
1649- return self .register_commands_for_agent ("claude" , manifest , extension_dir , project_root )
1695+ return self .register_commands_for_agent (
1696+ "claude" , manifest , extension_dir , project_root , link_outputs = link_outputs
1697+ )
16501698
16511699
16521700class ExtensionCatalog (CatalogStackBase ):
0 commit comments