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 `<cmd> --help`; the stale-skill warning
still runs, since it only reads. Free-text commands keep refreshing.
This commit is contained in:
berce authored and safishamsi committed 2026-10-20 11:28:03 +01:00
1 parent 848acdfcde
commit 5f4cccf0e7
3 files changed
+62 -12

No files matched your search

+12 -4
View File
@@ -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
+16 -4
View File
@@ -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"])
+34 -4
View File
@@ -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):