From 5f4cccf0e77fc23c593671b8c5b62227dab33457 Mon Sep 17 00:00:00 2001 From: berce <75228263+bercedev@users.noreply.github.com> Date: Mon, 19 Oct 2026 00:33:16 +0300 Subject: [PATCH] fix(cli): leave installed skills alone on version and help (#4545) _run_cli refreshed stale skills and old OpenCode plugins before the --version/--help early returns, so checking what was installed rewrote SKILL.md, left a .bak and restamped .graphify_version. Skip both refreshes for version, help and ` --help`; the stale-skill warning still runs, since it only reads. Free-text commands keep refreshing. --- graphify/__main__.py | 16 ++++++++--- tests/test_opencode_plugin_refresh.py | 20 +++++++++++--- tests/test_skill_auto_refresh.py | 38 ++++++++++++++++++++++++--- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/graphify/__main__.py b/graphify/__main__.py index 2b2f44dd7..2bf7ddcb9 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -831,12 +831,21 @@ def _run_cli() -> None: # Skip during hook-check — it runs on every editor tool use and must be silent. # Deduplicate paths so platforms sharing the same install dir don't warn twice. _silent_cmds = {"install", "uninstall", "hook-check", "hook-guard"} + _FREE_TEXT_CMDS = {"query", "explain", "path", "save-result", "install", "uninstall"} + _help_flags = {"-h", "--help", "-?"} + # Version and help only report, so they leave installed files alone (#4545). + _informational = ( + len(sys.argv) < 2 + or sys.argv[1] in {"-v", "--version", "version", *_help_flags} + or (sys.argv[1] not in _FREE_TEXT_CMDS and any(a in _help_flags for a in sys.argv[2:])) + ) if not any(arg in _silent_cmds for arg in sys.argv): # A package upgrade leaves every installed skill at the old version; # refresh them first, so the check below only fires for copies the # refresh could not or must not touch (#1805). - _refresh_stale_skills() - _refresh_v1_opencode_plugins() + if not _informational: + _refresh_stale_skills() + _refresh_v1_opencode_plugins() # Resolve each platform's real user-scope destination so per-platform # overrides (gemini, opencode, devin, antigravity, amp) check the dir # they actually install into, not the bare cfg['skill_dst']. @@ -1077,8 +1086,7 @@ def _run_cli() -> None: # (e.g. "cursor install --help" was silently installing into Cursor, #821). # Exempt: free-text commands (user string may contain these tokens), and # "install"/"uninstall" which have their own per-subcommand help handlers. - _FREE_TEXT_CMDS = {"query", "explain", "path", "save-result", "install", "uninstall"} - if cmd not in _FREE_TEXT_CMDS and any(a in {"-h", "--help", "-?"} for a in sys.argv[2:]): + if cmd not in _FREE_TEXT_CMDS and any(a in _help_flags for a in sys.argv[2:]): print(f"Run 'graphify --help' for full usage.") return diff --git a/tests/test_opencode_plugin_refresh.py b/tests/test_opencode_plugin_refresh.py index 98affd3b1..18cf3ec3e 100644 --- a/tests/test_opencode_plugin_refresh.py +++ b/tests/test_opencode_plugin_refresh.py @@ -375,16 +375,28 @@ def test_no_lock_file_is_made_where_there_is_nothing_to_refresh(tmp_path, monkey def test_the_first_cli_run_after_an_upgrade_rewrites_it(tmp_path, monkeypatch, capsys): + plugin, _config = _old_install(tmp_path) + monkeypatch.chdir(tmp_path) + monkeypatch.setattr(sys, "argv", ["graphify", "extract", "."]) + monkeypatch.setattr(mainmod, "dispatch_install_cli", lambda _cmd: False) + monkeypatch.setattr(mainmod, "dispatch_command", lambda _cmd: None) + + mainmod._run_cli() + + assert plugin.read_text(encoding="utf-8") == _OPENCODE_PLUGIN_JS + assert "graphify: refreshed" in capsys.readouterr().err + + +def test_version_leaves_an_old_plugin_alone(tmp_path, monkeypatch, capsys): + """Checking the version must not rewrite installed files (#4545).""" plugin, _config = _old_install(tmp_path) monkeypatch.chdir(tmp_path) monkeypatch.setattr(sys, "argv", ["graphify", "--version"]) mainmod._run_cli() - out, err = capsys.readouterr() - assert out.strip() == f"graphify {__version__}" - assert plugin.read_text(encoding="utf-8") == _OPENCODE_PLUGIN_JS - assert "graphify: refreshed" in err + assert capsys.readouterr().out.strip() == f"graphify {__version__}" + assert plugin.read_text(encoding="utf-8") == V1_PLUGIN @pytest.mark.parametrize("cmd", ["install", "uninstall", "hook-check", "hook-guard"]) diff --git a/tests/test_skill_auto_refresh.py b/tests/test_skill_auto_refresh.py index 462706623..3b0b15490 100644 --- a/tests/test_skill_auto_refresh.py +++ b/tests/test_skill_auto_refresh.py @@ -467,14 +467,44 @@ def test_a_stale_gemini_skill_gets_the_warning_too(monkeypatch, capsys): def test_the_first_cli_run_after_an_upgrade_refreshes_and_stays_quiet(monkeypatch, capsys): dsts = [_stale(name) for name in ("claude", "codex")] - monkeypatch.setattr(sys, "argv", ["graphify", "--version"]) + monkeypatch.setattr(sys, "argv", ["graphify", "extract", "."]) + monkeypatch.setattr(mainmod, "dispatch_install_cli", lambda _cmd: False) + monkeypatch.setattr(mainmod, "dispatch_command", lambda _cmd: None) mainmod._run_cli() - out, err = capsys.readouterr() - assert out.strip() == f"graphify {__version__}" assert all(_stamp(dst) == __version__ for dst in dsts) - assert "warning: skill at" not in err, "a refreshed copy must not also warn" + assert "warning: skill at" not in capsys.readouterr().err, "a refreshed copy must not also warn" + + +@pytest.mark.parametrize("argv", [ + [], ["--version"], ["-v"], ["version"], ["--help"], ["-h"], ["-?"], ["update", "--help"], +]) +def test_version_and_help_leave_a_stale_skill_alone(monkeypatch, capsys, argv): + """Checking what is installed must not rewrite it (#4545); the stale copy is + still reported, since the warning only reads.""" + dst = _stale("codex") + monkeypatch.setattr(sys, "argv", ["graphify", *argv]) + monkeypatch.setattr(mainmod, "dispatch_command", lambda _cmd: pytest.fail("help must not dispatch")) + + mainmod._run_cli() + + assert _stamp(dst) == OLD + assert dst.read_text(encoding="utf-8") == OLD_BODY + assert not dst.with_name("SKILL.md.bak").exists() + assert f"warning: skill at {dst.parent} is from graphify {OLD}" in capsys.readouterr().err + + +def test_help_text_given_to_a_free_text_command_still_refreshes(monkeypatch): + """`graphify query "what does --help do"` is a real query, not a help request.""" + dst = _stale("codex") + monkeypatch.setattr(sys, "argv", ["graphify", "query", "what does", "--help", "do"]) + monkeypatch.setattr(mainmod, "dispatch_install_cli", lambda _cmd: False) + monkeypatch.setattr(mainmod, "dispatch_command", lambda _cmd: None) + + mainmod._run_cli() + + assert _stamp(dst) == __version__ def test_what_the_refresh_skips_still_gets_the_warning(monkeypatch, capsys):