From a4d09aefd8fc441c98db8203440602350a8d75ae Mon Sep 17 00:00:00 2001 From: safishamsi Date: Tue, 23 Jun 2026 00:18:47 +0100 Subject: [PATCH] Install the hermes skill to %LOCALAPPDATA% on Windows (#1403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit graphify install --platform hermes always wrote the skill to ~/.hermes/skills, the POSIX path. On Windows, Hermes scans %LOCALAPPDATA%\hermes\skills, so the installed skill was never discovered. _platform_skill_destination now has a hermes branch: Windows -> %LOCALAPPDATA%\hermes\skills, other OSes unchanged (~/.hermes/skills). Pure path logic — no skillgen regeneration. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 1 + graphify/__main__.py | 9 +++++++++ tests/test_install.py | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b4cbf..a14674c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu ## Unreleased +- Fix: `graphify install --platform hermes` now installs to the right directory on Windows. Hermes scans `%LOCALAPPDATA%\hermes\skills`, but the installer always used the POSIX `~/.hermes/skills` (so on Windows the skill was never discovered). `_platform_skill_destination` gained a hermes branch that targets `%LOCALAPPDATA%\hermes\skills` on Windows and keeps `~/.hermes/skills` elsewhere (#1403). - Fix: cross-file type-annotation references no longer create phantom duplicate nodes. A class defined once but referenced via type annotations in N other files (`def f(x: Thing) -> Thing`) produced 1+N nodes — the extra ones with the referencing file's path baked into the id (`pkg_a_py_thing`). `ensure_named_node` minted a *sourced* stub for these cross-file refs, which `_disambiguate_colliding_node_ids` then collided into per-file ids and `_rewire_unique_stub_nodes` refused to collapse. The fallback now emits a *sourceless* stub (like the inheritance-base path), so the references resolve to the single canonical definition. Fixed uniformly across all six language extractors that share the helper (#1402). - Feat: CUDA (`.cu`/`.cuh`) source files are now extracted. CUDA is a C++ superset, so these files route through the existing C++ (`tree-sitter-cpp`) extractor — no new grammar dependency. `__global__`/`__device__` kernels, host functions, structs and `#include`s are captured, host call edges are inferred, and `<<>>` kernel-launch syntax parses without error. Detection and file-watching follow automatically since both derive their extension sets from the dispatch table / `CODE_EXTENSIONS` (#1411). diff --git a/graphify/__main__.py b/graphify/__main__.py index 038932a..6ce46ab 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -152,6 +152,15 @@ def _platform_skill_destination(platform_name: str, *, project: bool = False, pr return (project_dir or Path(".")) / ".opencode" / "skills" / "graphify" / "SKILL.md" return Path.home() / ".config" / "opencode" / "skills" / "graphify" / "SKILL.md" + if platform_name == "hermes": + if project: + return (project_dir or Path(".")) / ".hermes" / "skills" / "graphify" / "SKILL.md" + # On Windows, Hermes scans %LOCALAPPDATA%\hermes\skills, not ~/.hermes (#1403). + if platform.system() == "Windows": + local_appdata = Path(os.environ.get("LOCALAPPDATA") or (Path.home() / "AppData" / "Local")) + return local_appdata / "hermes" / "skills" / "graphify" / "SKILL.md" + return Path.home() / ".hermes" / "skills" / "graphify" / "SKILL.md" + if platform_name == "devin": if project: return (project_dir or Path(".")) / ".devin" / "skills" / "graphify" / "SKILL.md" diff --git a/tests/test_install.py b/tests/test_install.py index 7338809..fc2c165 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -992,3 +992,21 @@ def test_uninstall_all_removes_amp_user_skill(tmp_path, monkeypatch): main() assert not skill.exists() + + +def test_hermes_skill_destination_windows_uses_localappdata(): + """#1403: on Windows, Hermes scans %LOCALAPPDATA%\\hermes\\skills, so the global + skill must land there — not ~/.hermes/skills (the POSIX path).""" + from graphify.__main__ import _platform_skill_destination + with patch("graphify.__main__.platform.system", return_value="Windows"), \ + patch.dict(os.environ, {"LOCALAPPDATA": str(Path("/tmp/AppDataLocal"))}): + dst = _platform_skill_destination("hermes", project=False) + assert dst == Path("/tmp/AppDataLocal") / "hermes" / "skills" / "graphify" / "SKILL.md", dst + + +def test_hermes_skill_destination_posix_uses_home(): + """Non-Windows hermes destination is unchanged (~/.hermes/skills).""" + 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