From 49d17c5ae8cde8be2afcac15706a1914dbe1fc71 Mon Sep 17 00:00:00 2001 From: Matias Duarte Date: Sat, 6 Jun 2026 18:57:05 -0300 Subject: [PATCH 1/5] fix: use .codex instead of .agents for codex platform skill_dst --- graphify/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphify/__main__.py b/graphify/__main__.py index 8acd55a62..c59850cec 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -415,7 +415,7 @@ def _skill_registration(skill_path: str = "~/.claude/skills/graphify/SKILL.md") }, "codex": { "skill_file": "skill-codex.md", - "skill_dst": Path(".agents") / "skills" / "graphify" / "SKILL.md", + "skill_dst": Path(".codex") / "skills" / "graphify" / "SKILL.md", "claude_md": False, "skill_refs": "codex", }, From f3815267ba58651086fe29bfb53681a5d3f44d8a Mon Sep 17 00:00:00 2001 From: Matias Duarte Date: Sat, 6 Jun 2026 21:26:59 -0300 Subject: [PATCH 2/5] test: update codex assertions to point to .codex --- tests/test_install.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_install.py b/tests/test_install.py index 773e5567a..31476955b 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -8,7 +8,7 @@ PLATFORMS = { "claude": (".claude/skills/graphify/SKILL.md",), - "codex": (".agents/skills/graphify/SKILL.md",), + "codex": (".codex/skills/graphify/SKILL.md",), "opencode": (".config/opencode/skills/graphify/SKILL.md",), "kilo": ( ".config/kilo/skills/graphify/SKILL.md", @@ -41,7 +41,7 @@ def test_install_default_claude(tmp_path): def test_install_codex(tmp_path): _install(tmp_path, "codex") - assert (tmp_path / ".agents" / "skills" / "graphify" / "SKILL.md").exists() + assert (tmp_path / ".codex" / "skills" / "graphify" / "SKILL.md").exists() def test_install_opencode(tmp_path): @@ -87,10 +87,10 @@ def test_install_project_codex_writes_skill_and_agents(tmp_path, monkeypatch): monkeypatch.setattr(sys, "argv", ["graphify", "install", "--project", "--platform", "codex"]) with patch("graphify.__main__.Path.home", return_value=home): main() - assert (project / ".agents" / "skills" / "graphify" / "SKILL.md").exists() + assert (project / ".codex" / "skills" / "graphify" / "SKILL.md").exists() assert (project / "AGENTS.md").exists() assert (project / ".codex" / "hooks.json").exists() - assert not (home / ".agents" / "skills" / "graphify" / "SKILL.md").exists() + assert not (home / ".codex" / "skills" / "graphify" / "SKILL.md").exists() def test_claude_subcommand_project_install_and_uninstall_are_project_scoped(tmp_path, monkeypatch): @@ -124,14 +124,14 @@ def test_codex_subcommand_project_install_and_uninstall_are_project_scoped(tmp_p home = tmp_path / "home" project = tmp_path / "project" project.mkdir() - user_skill = home / ".agents" / "skills" / "graphify" / "SKILL.md" + user_skill = home / ".codex" / "skills" / "graphify" / "SKILL.md" user_skill.parent.mkdir(parents=True) user_skill.write_text("user skill") monkeypatch.chdir(project) with patch("graphify.__main__.Path.home", return_value=home): monkeypatch.setattr(sys, "argv", ["graphify", "codex", "install", "--project"]) main() - assert (project / ".agents" / "skills" / "graphify" / "SKILL.md").exists() + assert (project / ".codex" / "skills" / "graphify" / "SKILL.md").exists() assert (project / "AGENTS.md").exists() assert (project / ".codex" / "hooks.json").exists() assert user_skill.exists() @@ -140,7 +140,7 @@ def test_codex_subcommand_project_install_and_uninstall_are_project_scoped(tmp_p main() assert user_skill.exists() - assert not (project / ".agents" / "skills" / "graphify" / "SKILL.md").exists() + assert not (project / ".codex" / "skills" / "graphify" / "SKILL.md").exists() assert not (project / "AGENTS.md").exists() hooks_path = project / ".codex" / "hooks.json" assert hooks_path.exists() @@ -339,7 +339,7 @@ def test_uninstall_project_removes_project_skill_only(tmp_path, monkeypatch): home = tmp_path / "home" project = tmp_path / "project" project.mkdir() - user_skill = home / ".agents" / "skills" / "graphify" / "SKILL.md" + user_skill = home / ".codex" / "skills" / "graphify" / "SKILL.md" user_skill.parent.mkdir(parents=True) user_skill.write_text("user skill") monkeypatch.chdir(project) @@ -349,7 +349,7 @@ def test_uninstall_project_removes_project_skill_only(tmp_path, monkeypatch): monkeypatch.setattr(sys, "argv", ["graphify", "uninstall", "--project", "--platform", "codex"]) main() assert user_skill.exists() - assert not (project / ".agents" / "skills" / "graphify" / "SKILL.md").exists() + assert not (project / ".codex" / "skills" / "graphify" / "SKILL.md").exists() assert not (project / "AGENTS.md").exists() From 070602a65b47fdc994e85f166526b3b69cf428ad Mon Sep 17 00:00:00 2001 From: Matias Duarte Date: Sun, 7 Jun 2026 16:43:46 -0300 Subject: [PATCH 3/5] fix: gemini platform global install now writes to ~/.gemini/config/skills/ instead of ~/.agents/ or ~/.gemini/skills/ --- graphify/__main__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/graphify/__main__.py b/graphify/__main__.py index c59850cec..adfabe1db 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -131,9 +131,7 @@ def _platform_skill_destination(platform_name: str, *, project: bool = False, pr if platform_name == "gemini": if project: return (project_dir or Path(".")) / ".gemini" / "skills" / "graphify" / "SKILL.md" - if platform.system() == "Windows": - return Path.home() / ".agents" / "skills" / "graphify" / "SKILL.md" - return Path.home() / ".gemini" / "skills" / "graphify" / "SKILL.md" + return Path.home() / ".gemini" / "config" / "skills" / "graphify" / "SKILL.md" if platform_name == "opencode": if project: From 0969a456fb25544e34011181575a9fa608ae8ce7 Mon Sep 17 00:00:00 2001 From: Matias Duarte Date: Mon, 6 Jul 2026 19:05:53 -0300 Subject: [PATCH 4/5] test: Fix test assertions and env for install roundtrips and references --- tests/test_install_references.py | 2 +- tests/test_install_roundtrip.py | 67 ++++++++++++++++---------------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/tests/test_install_references.py b/tests/test_install_references.py index 0f6061a5b..1a79ccee5 100644 --- a/tests/test_install_references.py +++ b/tests/test_install_references.py @@ -304,7 +304,7 @@ def test_gemini_install_references_all_resolve(tmp_path): """ import re _install(tmp_path, "gemini") - skill = tmp_path / ".gemini" / "skills" / "graphify" / "SKILL.md" + skill = tmp_path / ".gemini" / "config" / "skills" / "graphify" / "SKILL.md" assert skill.exists() refdir = skill.parent / "references" assert refdir.is_dir() diff --git a/tests/test_install_roundtrip.py b/tests/test_install_roundtrip.py index 68216e6b3..58353862e 100644 --- a/tests/test_install_roundtrip.py +++ b/tests/test_install_roundtrip.py @@ -59,39 +59,40 @@ def test_skill_roundtrip_at_real_destination(platform, project, tmp_path, monkey monkeypatch.chdir(project_dir) with patch("graphify.__main__.Path.home", return_value=home): - dst = mainmod._platform_skill_destination( - platform, project=project, project_dir=project_dir - ) - # Sanity: a user-scope install must not write under the project dir, and - # vice versa, so the two scopes never collide in this test. - if project: - assert str(dst).startswith(str(project_dir)) - else: - assert str(dst).startswith(str(home)) - - returned = mainmod._copy_skill_file( - platform, project=project, project_dir=project_dir - ) - assert returned == dst - assert dst.exists(), f"{platform} ({'project' if project else 'user'}) skill not installed" - assert (dst.parent / ".graphify_version").read_text() == mainmod.__version__ - - refs = dst.parent / "references" - if _has_real_bundle(platform): - assert refs.is_dir(), f"{platform} ships a bundle but no references/ installed" - assert (refs / "extraction-spec.md").exists() - else: - assert not refs.exists(), f"{platform} is monolith but references/ appeared" - # No staging dir is ever left behind. - assert not (dst.parent / "references.tmp").exists() - - removed = mainmod._remove_skill_file( - platform, project=project, project_dir=project_dir - ) - assert removed - assert not dst.exists() - assert not (dst.parent / ".graphify_version").exists() - assert not refs.exists() + with patch.dict("os.environ", {"LOCALAPPDATA": str(home / "AppData" / "Local")}): + dst = mainmod._platform_skill_destination( + platform, project=project, project_dir=project_dir + ) + # Sanity: a user-scope install must not write under the project dir, and + # vice versa, so the two scopes never collide in this test. + if project and platform not in ("antigravity", "antigravity-windows", "gemini"): + assert str(dst).startswith(str(project_dir)) + else: + assert str(dst).startswith(str(home)) + + returned = mainmod._copy_skill_file( + platform, project=project, project_dir=project_dir + ) + assert returned == dst + assert dst.exists(), f"{platform} ({'project' if project else 'user'}) skill not installed" + assert (dst.parent / ".graphify_version").read_text() == mainmod.__version__ + + refs = dst.parent / "references" + if _has_real_bundle(platform): + assert refs.is_dir(), f"{platform} ships a bundle but no references/ installed" + assert (refs / "extraction-spec.md").exists() + else: + assert not refs.exists(), f"{platform} is monolith but references/ appeared" + # No staging dir is ever left behind. + assert not (dst.parent / "references.tmp").exists() + + removed = mainmod._remove_skill_file( + platform, project=project, project_dir=project_dir + ) + assert removed + assert not dst.exists() + assert not (dst.parent / ".graphify_version").exists() + assert not refs.exists() def test_amp_user_install_at_corrected_agents_path(tmp_path, monkeypatch): From b37224db5f4b37ff7735cf5adaca9c78f723c5ae Mon Sep 17 00:00:00 2001 From: Matias Duarte Date: Mon, 6 Jul 2026 19:10:35 -0300 Subject: [PATCH 5/5] fix: Resolve UnboundLocalError and test paths for gemini/antigravity skills --- graphify/__main__.py | 18 ++++++++-------- tests/test_antigravity_install.py | 36 ++++++++++++++++++++++--------- tests/test_install.py | 4 ++-- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/graphify/__main__.py b/graphify/__main__.py index 0bfaf2fb9..502c2e8c6 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -1220,15 +1220,15 @@ def _antigravity_uninstall(project_dir: Path, *, project: bool = False) -> None: refs_dir = skill_dst.parent / "references" if refs_dir.exists(): shutil.rmtree(refs_dir) - for d in ( - skill_dst.parent, - skill_dst.parent.parent, - skill_dst.parent.parent.parent, - ): - try: - d.rmdir() - except OSError: - break + for d in ( + skill_dst.parent, + skill_dst.parent.parent, + skill_dst.parent.parent.parent, + ): + try: + d.rmdir() + except OSError: + break _CURSOR_RULE_PATH = Path(".cursor") / "rules" / "graphify.mdc" diff --git a/tests/test_antigravity_install.py b/tests/test_antigravity_install.py index 78c7e87fd..697902165 100644 --- a/tests/test_antigravity_install.py +++ b/tests/test_antigravity_install.py @@ -8,12 +8,21 @@ import graphify.__main__ as m +from unittest.mock import patch + def test_antigravity_project_install_writes_rules_and_workflows(tmp_path): - m._project_install("antigravity", tmp_path) - skill = tmp_path / ".agents" / "skills" / "graphify" / "SKILL.md" - rules = tmp_path / ".agents" / "rules" / "graphify.md" - workflow = tmp_path / ".agents" / "workflows" / "graphify.md" - assert skill.exists(), "skill should be installed under .agents/skills/" + home = tmp_path / "home" + proj = tmp_path / "proj" + home.mkdir() + proj.mkdir() + with patch("graphify.__main__.Path.home", return_value=home): + m._project_install("antigravity", proj) + + skill = home / ".gemini" / "config" / "skills" / "graphify" / "SKILL.md" + rules = proj / ".agents" / "rules" / "graphify.md" + workflow = proj / ".agents" / "workflows" / "graphify.md" + + assert skill.exists(), "skill should be installed globally under ~/.gemini/config/skills/" assert rules.exists(), "antigravity rules (always-on) must be written" assert workflow.exists(), "antigravity workflow must be written" # native tool-discovery frontmatter is injected into the skill @@ -21,8 +30,15 @@ def test_antigravity_project_install_writes_rules_and_workflows(tmp_path): def test_antigravity_project_uninstall_clears_rules_and_workflows(tmp_path): - m._project_install("antigravity", tmp_path) - m._project_uninstall("antigravity", tmp_path) - assert not (tmp_path / ".agents" / "rules" / "graphify.md").exists() - assert not (tmp_path / ".agents" / "workflows" / "graphify.md").exists() - assert not (tmp_path / ".agents" / "skills" / "graphify" / "SKILL.md").exists() + home = tmp_path / "home" + proj = tmp_path / "proj" + home.mkdir() + proj.mkdir() + with patch("graphify.__main__.Path.home", return_value=home): + m._project_install("antigravity", proj) + m._project_uninstall("antigravity", proj) + + assert not (proj / ".agents" / "rules" / "graphify.md").exists() + assert not (proj / ".agents" / "workflows" / "graphify.md").exists() + # The global skill should remain intact after a project uninstall. + assert (home / ".gemini" / "config" / "skills" / "graphify" / "SKILL.md").exists() diff --git a/tests/test_install.py b/tests/test_install.py index b954e9b5e..56eee2317 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -225,7 +225,7 @@ def test_codex_skill_uses_graphify_with_existing_graph(): fast-path block, which jumps straight to the query flow when a graph exists. """ import graphify - skill = (Path(graphify.__file__).parent / "skill-codex.md").read_text() + skill = (Path(graphify.__file__).parent / "skill-codex.md").read_text(encoding="utf-8") assert "Fast path — existing graph" in skill assert "skip Steps 1–5 entirely and jump straight to `## For /graphify query`" in skill assert "graphify query" in skill @@ -1054,4 +1054,4 @@ def test_hermes_skill_destination_posix_uses_home(): from graphify.__main__ import _platform_skill_destination with patch("graphify.__main__.platform.system", return_value="Linux"): dst = _platform_skill_destination("hermes", project=False) - assert str(dst).endswith(".hermes/skills/graphify/SKILL.md"), dst + assert dst.as_posix().endswith(".hermes/skills/graphify/SKILL.md"), dst