@@ -2248,6 +2248,50 @@ def test_dev_register_commands_symlinks_rendered_copilot_agent(
22482248 assert target .is_file ()
22492249 assert "Extension: test-ext" in cmd_file .read_text (encoding = "utf-8" )
22502250
2251+ def test_dev_register_commands_replaces_codex_dev_symlink (
2252+ self , extension_dir , project_dir , temp_dir
2253+ ):
2254+ """Codex dev registration should replace prior symlinks with real files."""
2255+ if not can_create_symlink (temp_dir ):
2256+ pytest .skip ("Current platform/user cannot create symlinks" )
2257+
2258+ skill_file = (
2259+ project_dir
2260+ / ".agents"
2261+ / "skills"
2262+ / "speckit-test-ext-hello"
2263+ / "SKILL.md"
2264+ )
2265+ skill_file .parent .mkdir (parents = True )
2266+ cache_file = (
2267+ extension_dir
2268+ / ".specify-dev"
2269+ / "agent-commands"
2270+ / "codex"
2271+ / "speckit-test-ext-hello"
2272+ / "SKILL.md"
2273+ )
2274+ cache_file .parent .mkdir (parents = True )
2275+ cache_file .write_text ("old linked content" , encoding = "utf-8" )
2276+ os .symlink (os .path .relpath (cache_file , skill_file .parent ), skill_file )
2277+
2278+ manifest = ExtensionManifest (extension_dir / "extension.yml" )
2279+ registrar = CommandRegistrar ()
2280+ registrar .register_commands_for_agent (
2281+ "codex" ,
2282+ manifest ,
2283+ extension_dir ,
2284+ project_dir ,
2285+ link_outputs = True ,
2286+ )
2287+
2288+ assert skill_file .exists ()
2289+ assert not skill_file .is_symlink ()
2290+ assert "name: speckit-test-ext-hello" in skill_file .read_text (
2291+ encoding = "utf-8"
2292+ )
2293+ assert cache_file .read_text (encoding = "utf-8" ) == "old linked content"
2294+
22512295 def test_dev_register_commands_falls_back_to_copy_when_symlink_fails (
22522296 self , extension_dir , project_dir , monkeypatch
22532297 ):
@@ -4874,6 +4918,93 @@ def test_add_dev_links_copilot_agent_when_supported(
48744918 else :
48754919 assert not agent_file .is_symlink ()
48764920
4921+ def test_add_dev_writes_codex_skills_as_files (self , extension_dir , project_dir ):
4922+ """Codex dev skills should be written as files so Codex can load them."""
4923+ from typer .testing import CliRunner
4924+ from unittest .mock import patch
4925+ from specify_cli import app
4926+
4927+ init_options = project_dir / ".specify" / "init-options.json"
4928+ init_options .write_text (
4929+ json .dumps ({"ai" : "codex" , "ai_skills" : True }), encoding = "utf-8"
4930+ )
4931+
4932+ runner = CliRunner ()
4933+ with patch .object (Path , "cwd" , return_value = project_dir ):
4934+ result = runner .invoke (
4935+ app ,
4936+ ["extension" , "add" , str (extension_dir ), "--dev" ],
4937+ catch_exceptions = True ,
4938+ )
4939+
4940+ assert result .exit_code == 0 , result .output
4941+
4942+ skill_file = (
4943+ project_dir
4944+ / ".agents"
4945+ / "skills"
4946+ / "speckit-test-ext-hello"
4947+ / "SKILL.md"
4948+ )
4949+ assert skill_file .exists ()
4950+ assert not skill_file .is_symlink ()
4951+
4952+ content = skill_file .read_text (encoding = "utf-8" )
4953+ assert "name: speckit-test-ext-hello" in content
4954+ assert "metadata:" in content
4955+ assert "source: test-ext:commands/hello.md" in content
4956+
4957+ def test_add_dev_replaces_existing_codex_skill_symlink (
4958+ self , extension_dir , project_dir , temp_dir
4959+ ):
4960+ """Codex dev installs should migrate expected dev symlinks to files."""
4961+ if not can_create_symlink (temp_dir ):
4962+ pytest .skip ("Current platform/user cannot create symlinks" )
4963+
4964+ from typer .testing import CliRunner
4965+ from unittest .mock import patch
4966+ from specify_cli import app
4967+
4968+ init_options = project_dir / ".specify" / "init-options.json"
4969+ init_options .write_text (
4970+ json .dumps ({"ai" : "codex" , "ai_skills" : True }), encoding = "utf-8"
4971+ )
4972+
4973+ skill_file = (
4974+ project_dir
4975+ / ".agents"
4976+ / "skills"
4977+ / "speckit-test-ext-hello"
4978+ / "SKILL.md"
4979+ )
4980+ skill_file .parent .mkdir (parents = True )
4981+ cache_file = (
4982+ extension_dir
4983+ / ".specify-dev"
4984+ / "extension-skills"
4985+ / "speckit-test-ext-hello"
4986+ / "SKILL.md"
4987+ )
4988+ cache_file .parent .mkdir (parents = True )
4989+ cache_file .write_text ("old linked content" , encoding = "utf-8" )
4990+ os .symlink (os .path .relpath (cache_file , skill_file .parent ), skill_file )
4991+
4992+ runner = CliRunner ()
4993+ with patch .object (Path , "cwd" , return_value = project_dir ):
4994+ result = runner .invoke (
4995+ app ,
4996+ ["extension" , "add" , str (extension_dir ), "--dev" ],
4997+ catch_exceptions = True ,
4998+ )
4999+
5000+ assert result .exit_code == 0 , result .output
5001+ assert skill_file .exists ()
5002+ assert not skill_file .is_symlink ()
5003+ content = skill_file .read_text (encoding = "utf-8" )
5004+ assert "name: speckit-test-ext-hello" in content
5005+ assert "source: test-ext:commands/hello.md" in content
5006+ assert cache_file .read_text (encoding = "utf-8" ) == "old linked content"
5007+
48775008 def test_add_dev_falls_back_to_copy_when_windows_symlinks_unavailable (
48785009 self , extension_dir , project_dir , monkeypatch
48795010 ):
0 commit comments