Skip to content

Commit 9736af9

Browse files
committed
fix(codex): defer legacy .codex cleanup until after skills fallback
1 parent 94ec86e commit 9736af9

2 files changed

Lines changed: 64 additions & 9 deletions

File tree

src/specify_cli/__init__.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,10 +1031,6 @@ def download_and_extract_template(
10311031
console.print("[cyan]Found nested directory structure[/cyan]")
10321032

10331033
for item in source_dir.iterdir():
1034-
# Codex skills mode should not materialize legacy prompt files
1035-
# from older template archives.
1036-
if skip_legacy_codex_prompts and ai_assistant == "codex" and item.name == ".codex":
1037-
continue
10381034
dest_path = project_path / item.name
10391035
if item.is_dir():
10401036
if dest_path.exists():
@@ -1085,11 +1081,6 @@ def download_and_extract_template(
10851081
elif verbose:
10861082
console.print("[cyan]Flattened nested directory structure[/cyan]")
10871083

1088-
if skip_legacy_codex_prompts and ai_assistant == "codex":
1089-
legacy_codex_dir = project_path / ".codex"
1090-
if legacy_codex_dir.is_dir():
1091-
shutil.rmtree(legacy_codex_dir, ignore_errors=True)
1092-
10931084
except Exception as e:
10941085
if tracker:
10951086
tracker.error("extract", str(e))
@@ -2121,6 +2112,14 @@ def init(
21212112
# so leaving stale commands is non-fatal.
21222113
console.print("[yellow]Warning: could not remove extracted commands directory[/yellow]")
21232114

2115+
# In Codex skills mode, remove legacy prompt layout after skills
2116+
# installation/fallback completes so prompt artifacts are not left
2117+
# behind in the project tree.
2118+
if selected_ai == "codex" and ai_skills:
2119+
legacy_codex_dir = project_path / ".codex"
2120+
if legacy_codex_dir.is_dir():
2121+
shutil.rmtree(legacy_codex_dir, ignore_errors=True)
2122+
21242123
if not no_git:
21252124
tracker.start("git")
21262125
if is_git_repo(project_path):

tests/test_ai_skills.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,62 @@ def fake_download(project_path, *args, **kwargs):
770770
assert result.exit_code == 0
771771
mock_skills.assert_called_once()
772772
assert mock_skills.call_args.kwargs.get("overwrite_existing") is True
773+
assert not (target / ".codex").exists()
774+
775+
def test_codex_ai_skills_removes_legacy_codex_dir_after_fallback(self, tmp_path):
776+
"""Codex skills init should not leave legacy .codex prompt directory behind."""
777+
from typer.testing import CliRunner
778+
779+
runner = CliRunner()
780+
target = tmp_path / "codex-cleanup-new"
781+
782+
def fake_download(project_path, *args, **kwargs):
783+
prompts_dir = project_path / ".codex" / "prompts"
784+
prompts_dir.mkdir(parents=True, exist_ok=True)
785+
(prompts_dir / "speckit.specify.md").write_text("---\ndescription: Legacy prompt\n---\n\nBody.\n")
786+
787+
with patch("specify_cli.download_and_extract_template", side_effect=fake_download), \
788+
patch("specify_cli.ensure_executable_scripts"), \
789+
patch("specify_cli.ensure_constitution_from_template"), \
790+
patch("specify_cli.install_ai_skills", return_value=True), \
791+
patch("specify_cli.is_git_repo", return_value=False), \
792+
patch("specify_cli.shutil.which", return_value="/usr/bin/codex"):
793+
result = runner.invoke(
794+
app,
795+
["init", str(target), "--ai", "codex", "--ai-skills", "--script", "sh", "--no-git"],
796+
)
797+
798+
assert result.exit_code == 0
799+
assert not (target / ".codex").exists()
800+
801+
def test_codex_ai_skills_here_mode_removes_legacy_codex_dir(self, tmp_path, monkeypatch):
802+
"""Codex --here skills init should not leave legacy .codex prompt directory behind."""
803+
from typer.testing import CliRunner
804+
805+
runner = CliRunner()
806+
target = tmp_path / "codex-cleanup-here"
807+
target.mkdir()
808+
monkeypatch.chdir(target)
809+
810+
def fake_download(project_path, *args, **kwargs):
811+
prompts_dir = project_path / ".codex" / "prompts"
812+
prompts_dir.mkdir(parents=True, exist_ok=True)
813+
(prompts_dir / "speckit.specify.md").write_text("---\ndescription: Legacy prompt\n---\n\nBody.\n")
814+
815+
with patch("specify_cli.download_and_extract_template", side_effect=fake_download), \
816+
patch("specify_cli.ensure_executable_scripts"), \
817+
patch("specify_cli.ensure_constitution_from_template"), \
818+
patch("specify_cli.install_ai_skills", return_value=True), \
819+
patch("specify_cli.is_git_repo", return_value=True), \
820+
patch("specify_cli.shutil.which", return_value="/usr/bin/codex"):
821+
result = runner.invoke(
822+
app,
823+
["init", "--here", "--ai", "codex", "--ai-skills", "--script", "sh", "--no-git"],
824+
input="y\n",
825+
)
826+
827+
assert result.exit_code == 0
828+
assert not (target / ".codex").exists()
773829

774830
def test_commands_preserved_when_skills_fail(self, tmp_path):
775831
"""If skills fail, commands should NOT be removed (safety net)."""

0 commit comments

Comments
 (0)