simplify(compat): hermes_cli small facades — drop 7 re-exports/aliases (+relay_runtime alias module), repoint 12 callers/tests

This commit is contained in:
Teknium
2026-09-03 13:05:57 -07:00
parent b39948106a
commit eeb7671e69
46 changed files with 171 additions and 231 deletions
+7 -7
View File
@@ -24,7 +24,7 @@ def cli_obj(_isolate):
"display": {"tool_progress": "new"},
"terminal": {},
}), patch("cli.get_tool_definitions", return_value=[]), \
patch("cli.build_welcome_banner"):
patch("hermes_cli.banner.build_welcome_banner"):
from cli import HermesCLI
obj = HermesCLI.__new__(HermesCLI)
obj.model = "test-model"
@@ -50,7 +50,7 @@ class TestLowContextWarning:
"""Warning shown when context is below Hermes' minimum."""
cli_obj.agent.context_compressor.context_length = 32768
with patch("cli.get_tool_definitions", return_value=[]), \
patch("cli.build_welcome_banner"):
patch("hermes_cli.banner.build_welcome_banner"):
cli_obj.show_banner()
calls = [str(c) for c in cli_obj.console.print.call_args_list]
@@ -64,7 +64,7 @@ class TestLowContextWarning:
"""Warning shown for 2048 tokens (common LM Studio default)."""
cli_obj.agent.context_compressor.context_length = 2048
with patch("cli.get_tool_definitions", return_value=[]), \
patch("cli.build_welcome_banner"):
patch("hermes_cli.banner.build_welcome_banner"):
cli_obj.show_banner()
calls = [str(c) for c in cli_obj.console.print.call_args_list]
@@ -75,7 +75,7 @@ class TestLowContextWarning:
"""No warning at exactly Hermes' minimum context length."""
cli_obj.agent.context_compressor.context_length = MINIMUM_CONTEXT_LENGTH
with patch("cli.get_tool_definitions", return_value=[]), \
patch("cli.build_welcome_banner"):
patch("hermes_cli.banner.build_welcome_banner"):
cli_obj.show_banner()
calls = [str(c) for c in cli_obj.console.print.call_args_list]
@@ -86,7 +86,7 @@ class TestLowContextWarning:
"""No warning above Hermes' minimum context length."""
cli_obj.agent.context_compressor.context_length = MINIMUM_CONTEXT_LENGTH + 1
with patch("cli.get_tool_definitions", return_value=[]), \
patch("cli.build_welcome_banner"):
patch("hermes_cli.banner.build_welcome_banner"):
cli_obj.show_banner()
calls = [str(c) for c in cli_obj.console.print.call_args_list]
@@ -98,7 +98,7 @@ class TestLowContextWarning:
cli_obj.agent.context_compressor.context_length = 4096
cli_obj.base_url = "http://localhost:11434/v1"
with patch("cli.get_tool_definitions", return_value=[]), \
patch("cli.build_welcome_banner"):
patch("hermes_cli.banner.build_welcome_banner"):
cli_obj.show_banner()
calls = [str(c) for c in cli_obj.console.print.call_args_list]
@@ -112,7 +112,7 @@ class TestLowContextWarning:
cli_obj.agent.context_compressor.context_length = 4096
cli_obj.base_url = "http://localhost:8080/v1"
with patch("cli.get_tool_definitions", return_value=[]), \
patch("cli.build_welcome_banner"):
patch("hermes_cli.banner.build_welcome_banner"):
cli_obj.show_banner()
calls = [str(c) for c in cli_obj.console.print.call_args_list]
+1 -1
View File
@@ -140,7 +140,7 @@ def test_show_banner_does_not_print_skills():
cli_obj.preloaded_skills = ["hermes-agent-dev", "github-auth"]
cli_obj.console = MagicMock()
with patch("cli.build_welcome_banner") as mock_banner, patch(
with patch("hermes_cli.banner.build_welcome_banner") as mock_banner, patch(
"shutil.get_terminal_size", return_value=os.terminal_size((120, 40))
):
cli_obj.show_banner()
+1 -1
View File
@@ -249,7 +249,7 @@ def test_runtime_resolution_failure_is_not_sticky(monkeypatch):
monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
monkeypatch.setattr(cli, "AIAgent", _DummyAgent)
monkeypatch.setattr("run_agent.AIAgent", _DummyAgent)
shell = cli.HermesCLI(model="gpt-5", compact=True, max_turns=1)
+2 -2
View File
@@ -78,7 +78,7 @@ def test_show_session_status_prints_gateway_style_summary():
"started_at": 1775791440,
}
with patch("cli.display_hermes_home", return_value="~/.hermes"):
with patch("hermes_constants.display_hermes_home", return_value="~/.hermes"):
cli_obj._show_session_status()
printed = "\n".join(str(call.args[0]) for call in cli_obj.console.print.call_args_list)
@@ -107,7 +107,7 @@ def test_show_session_status_includes_reasoning_approvals_context():
"context_tokens": 50000, "context_length": 200000, "context_percent": 25,
}
with patch("cli.display_hermes_home", return_value="~/.hermes"), \
with patch("hermes_constants.display_hermes_home", return_value="~/.hermes"), \
patch("tools.approval._get_approval_mode", return_value="manual"), \
patch("tools.approval.is_approval_bypass_active_for_session", return_value=False):
cli_obj._show_session_status()
@@ -5,7 +5,12 @@ leak into the input buffer after terminal resize storms or multiplexer
tab switches — see issue #14692.
"""
from cli import _strip_leaked_terminal_responses
from cli import _strip_leaked_terminal_responses_with_meta
def _strip_leaked_terminal_responses(text: str) -> str:
cleaned, _ = _strip_leaked_terminal_responses_with_meta(text)
return cleaned
class TestStripLeakedTerminalResponses:
+25 -21
View File
@@ -8,6 +8,8 @@ import os
import shutil
import subprocess
import pytest
from hermes_cli import worktree_ops
from pathlib import Path
@@ -845,7 +847,7 @@ class TestWorktreeLockReaping:
import cli
wt = self._mk(cli, git_repo, "hermes-dead", pid=999999)
# sanity: this is the accumulation bug — remove --force alone can't do it
assert cli._worktree_lock_is_live(str(git_repo), str(wt)) == "dead"
assert worktree_ops._worktree_lock_is_live(str(git_repo), str(wt)) == "dead"
cli._prune_stale_worktrees(str(git_repo))
assert not wt.exists(), "dead-locked clean worktree should be unlocked + reaped"
@@ -899,19 +901,19 @@ class TestWorktreeLockPredicate:
["git", "worktree", "add", str(p), "-b", "hermes/hermes-x", "HEAD"],
cwd=git_repo, capture_output=True,
)
assert cli._worktree_lock_is_live(str(git_repo), str(p)) is None
assert worktree_ops._worktree_lock_is_live(str(git_repo), str(p)) is None
def test_foreign_lock_reason_returns_dead(self, git_repo):
import cli
p = self._mk_locked(git_repo, "hermes-foreign", "some other tool")
assert cli._worktree_lock_is_live(str(git_repo), str(p)) == "dead"
assert worktree_ops._worktree_lock_is_live(str(git_repo), str(p)) == "dead"
def test_bad_repo_root_fails_safe_to_live(self, tmp_path):
import cli
# Not a git repo -> git query fails -> must report "live" (never delete)
assert cli._worktree_lock_is_live(str(tmp_path), str(tmp_path / "x")) == "live"
assert worktree_ops._worktree_lock_is_live(str(tmp_path), str(tmp_path / "x")) == "live"
class TestWidenedPruner:
@@ -1021,7 +1023,7 @@ class TestWidenedPruner:
["git", "worktree", "add", str(p), "-b", "wt/noremote", "HEAD"],
cwd=repo, capture_output=True,
)
assert cli._worktree_commits_all_merged_upstream(str(p)) is False
assert worktree_ops._worktree_commits_all_merged_upstream(str(p)) is False
# -- preserved-work warning ----------------------------------------------
@@ -1050,10 +1052,10 @@ class TestMergeVerdictCache:
wt, sha = self._mk(git_repo, "hermes-cachehit", commit=True)
self._merge_upstream(git_repo, sha)
uncached = cli._worktree_commits_all_merged_upstream(str(wt))
uncached = worktree_ops._worktree_commits_all_merged_upstream(str(wt))
cache = {}
cold = cli._worktree_commits_all_merged_upstream(str(wt), cache=cache)
warm = cli._worktree_commits_all_merged_upstream(str(wt), cache=cache)
cold = worktree_ops._worktree_commits_all_merged_upstream(str(wt), cache=cache)
warm = worktree_ops._worktree_commits_all_merged_upstream(str(wt), cache=cache)
assert cache, "verdict should have been memoized"
assert uncached is cold is warm is True
@@ -1070,7 +1072,7 @@ class TestMergeVerdictCache:
self._merge_upstream(git_repo, sha)
cache = {}
assert cli._worktree_commits_all_merged_upstream(str(wt), cache=cache) is True
assert worktree_ops._worktree_commits_all_merged_upstream(str(wt), cache=cache) is True
key_after_merge = set(cache)
# New local-only work lands in the worktree.
@@ -1078,7 +1080,7 @@ class TestMergeVerdictCache:
subprocess.run(["git", "add", "more.txt"], cwd=wt, capture_output=True)
subprocess.run(["git", "commit", "-m", "new work"], cwd=wt, capture_output=True)
assert cli._worktree_commits_all_merged_upstream(str(wt), cache=cache) is False
assert worktree_ops._worktree_commits_all_merged_upstream(str(wt), cache=cache) is False
assert set(cache) != key_after_merge, "moved HEAD must produce a new key"
@@ -1091,8 +1093,8 @@ class TestMergeVerdictCache:
monkeypatch.setattr(worktree_ops, "_worktree_merge_cache_path", lambda: path)
monkeypatch.setattr(worktree_ops, "_WORKTREE_MERGE_CACHE_MAX", 10)
cli._save_worktree_merge_cache({f"sha{i}..sha{i}:20": True for i in range(50)})
assert len(cli._load_worktree_merge_cache()) == 10
worktree_ops._save_worktree_merge_cache({f"sha{i}..sha{i}:20": True for i in range(50)})
assert len(worktree_ops._load_worktree_merge_cache()) == 10
class TestPruneParallelEquivalence:
@@ -1151,7 +1153,7 @@ class TestPruneParallelEquivalence:
# Phase B — an independent board (distinct branch names so creation
# can't collide with phase A's leftover refs) run through a real pool.
try:
cli._worktree_merge_cache_path().unlink()
worktree_ops._worktree_merge_cache_path().unlink()
except Exception:
pass
board2 = self._board(git_repo, tag="b")
@@ -1177,7 +1179,9 @@ class TestPruneParallelEquivalence:
def __init__(self, *a, **kw):
raise RuntimeError("cannot start thread")
monkeypatch.setattr(cli.concurrent.futures, "ThreadPoolExecutor", _Boom)
import concurrent.futures
monkeypatch.setattr(concurrent.futures, "ThreadPoolExecutor", _Boom)
monkeypatch.setattr(cli.os, "cpu_count", lambda: 8)
cli._prune_stale_worktrees(str(git_repo))
@@ -1289,7 +1293,7 @@ class TestShallowCloneDeepening:
_, clone, wt = self._stuck_worktree(tmp_path)
assert cli._worktree_has_unpushed_commits(str(wt))
assert cli._deepen_shallow_repo(str(clone)) is True
assert worktree_ops._deepen_shallow_repo(str(clone)) is True
assert not cli._repo_is_shallow(str(clone))
assert not cli._worktree_has_unpushed_commits(str(wt)), (
"after deepening, the worktree's HEAD is an ancestor of "
@@ -1317,7 +1321,7 @@ class TestShallowCloneDeepening:
["git", "remote", "set-url", "origin", f"file://{tmp_path}/gone"],
clone,
)
assert cli._deepen_shallow_repo(str(clone), timeout=30) is False
assert worktree_ops._deepen_shallow_repo(str(clone), timeout=30) is False
cli._prune_stale_worktrees(str(clone))
assert wt.exists(), (
"offline deepen failure must fall back to preserving the tree"
@@ -1325,7 +1329,7 @@ class TestShallowCloneDeepening:
def test_deepen_noop_on_full_clone(self, git_repo):
import cli
assert cli._deepen_shallow_repo(str(git_repo)) is True
assert worktree_ops._deepen_shallow_repo(str(git_repo)) is True
def test_real_unpushed_work_survives_deepening(self, tmp_path):
"""Deepening must not turn genuinely unpushed commits reapable."""
@@ -1384,7 +1388,7 @@ class TestPrMergedEscapeHatch:
def test_merged_pr_tree_is_reaped(self, git_repo, tmp_path, monkeypatch):
import cli
wt = self._mk_diverged(git_repo, "hermes-rebase-merged")
assert cli._worktree_commits_all_merged_upstream(str(wt)) is False, (
assert worktree_ops._worktree_commits_all_merged_upstream(str(wt)) is False, (
"precondition: cherry must NOT consider this merged — the PR "
"check is the only thing that can reap it"
)
@@ -1426,19 +1430,19 @@ class TestPrMergedEscapeHatch:
wt = self._mk_diverged(git_repo, "hermes-memo")
self._stub_gh(tmp_path, monkeypatch)
cache: dict = {}
assert cli._worktree_branch_pr_merged(str(wt), cache=cache) is True
assert worktree_ops._worktree_branch_pr_merged(str(wt), cache=cache) is True
keys = [k for k in cache if k.startswith("pr-merged:")]
assert len(keys) == 1 and cache[keys[0]] is True
# Break gh: a cached True verdict must not re-consult it.
self._stub_gh(tmp_path, monkeypatch, stdout="", exit_code=1)
assert cli._worktree_branch_pr_merged(str(wt), cache=cache) is True
assert worktree_ops._worktree_branch_pr_merged(str(wt), cache=cache) is True
def test_negative_verdict_not_cached(self, git_repo, tmp_path, monkeypatch):
import cli
wt = self._mk_diverged(git_repo, "hermes-nocache-neg")
self._stub_gh(tmp_path, monkeypatch, stdout="[]")
cache: dict = {}
assert cli._worktree_branch_pr_merged(str(wt), cache=cache) is False
assert worktree_ops._worktree_branch_pr_merged(str(wt), cache=cache) is False
assert not [k for k in cache if k.startswith("pr-merged:")], (
"False must not be memoized — the PR can merge later with the "
"same (branch, head) key"
+12 -10
View File
@@ -15,6 +15,8 @@ import time
import pytest
from hermes_cli import worktree_ops
def _run(args, cwd):
return subprocess.run(
@@ -93,7 +95,7 @@ class TestFetchRemoteBranchHeads:
import cli
repo = repo_with_bare_origin
_mk_worktree(repo, "hermes-a", "hermes/hermes-a", push=True)
heads = cli._fetch_remote_branch_heads(str(repo))
heads = worktree_ops._fetch_remote_branch_heads(str(repo))
assert heads is not None
assert "main" in heads
assert "hermes/hermes-a" in heads
@@ -104,7 +106,7 @@ class TestFetchRemoteBranchHeads:
repo.mkdir()
_run(["git", "init"], repo)
_run(["git", "remote", "add", "origin", str(tmp_path / "missing.git")], repo)
assert cli._fetch_remote_branch_heads(str(repo)) is None
assert worktree_ops._fetch_remote_branch_heads(str(repo)) is None
class TestBranchPushedExact:
@@ -112,8 +114,8 @@ class TestBranchPushedExact:
import cli
repo = repo_with_bare_origin
wt = _mk_worktree(repo, "hermes-x", "hermes/hermes-x", push=True)
heads = cli._fetch_remote_branch_heads(str(repo))
assert cli._worktree_branch_pushed_exact(str(wt), heads) is True
heads = worktree_ops._fetch_remote_branch_heads(str(repo))
assert worktree_ops._worktree_branch_pushed_exact(str(wt), heads) is True
def test_local_ahead_of_push_false(self, repo_with_bare_origin):
import cli
@@ -122,22 +124,22 @@ class TestBranchPushedExact:
repo, "hermes-y", "hermes/hermes-y",
push=True, extra_commit_after_push=True,
)
heads = cli._fetch_remote_branch_heads(str(repo))
assert cli._worktree_branch_pushed_exact(str(wt), heads) is False
heads = worktree_ops._fetch_remote_branch_heads(str(repo))
assert worktree_ops._worktree_branch_pushed_exact(str(wt), heads) is False
def test_never_pushed_false(self, repo_with_bare_origin):
import cli
repo = repo_with_bare_origin
wt = _mk_worktree(repo, "hermes-z", "hermes/hermes-z", push=False)
heads = cli._fetch_remote_branch_heads(str(repo))
assert cli._worktree_branch_pushed_exact(str(wt), heads) is False
heads = worktree_ops._fetch_remote_branch_heads(str(repo))
assert worktree_ops._worktree_branch_pushed_exact(str(wt), heads) is False
def test_none_heads_false(self, repo_with_bare_origin):
import cli
repo = repo_with_bare_origin
wt = _mk_worktree(repo, "hermes-n", "hermes/hermes-n", push=True)
assert cli._worktree_branch_pushed_exact(str(wt), None) is False
assert cli._worktree_branch_pushed_exact(str(wt), {}) is False
assert worktree_ops._worktree_branch_pushed_exact(str(wt), None) is False
assert worktree_ops._worktree_branch_pushed_exact(str(wt), {}) is False
class TestStartupPrunerPushedTier:
+3 -3
View File
@@ -53,7 +53,7 @@ class TestCleanupFailedWorktreeAdd:
return wt
def test_sweeps_dir_admin_entry_and_branch(self, repo):
from cli import _cleanup_failed_worktree_add
from hermes_cli.worktree_ops import _cleanup_failed_worktree_add
wt = self._simulate_timed_out_add(repo)
admin = repo / ".git" / "worktrees" / "hermes-dead00"
@@ -68,7 +68,7 @@ class TestCleanupFailedWorktreeAdd:
def test_retry_succeeds_after_cleanup(self, repo):
"""The whole point: the same worktree name is creatable again."""
from cli import _cleanup_failed_worktree_add
from hermes_cli.worktree_ops import _cleanup_failed_worktree_add
wt = self._simulate_timed_out_add(repo)
_cleanup_failed_worktree_add(str(repo), wt, "hermes/hermes-dead00")
@@ -80,7 +80,7 @@ class TestCleanupFailedWorktreeAdd:
def test_noop_when_nothing_exists(self, repo):
"""Fail-soft on an error path where git never created anything."""
from cli import _cleanup_failed_worktree_add
from hermes_cli.worktree_ops import _cleanup_failed_worktree_add
_cleanup_failed_worktree_add(
str(repo), repo / ".worktrees" / "never-existed", "hermes/never-existed"
+9 -7
View File
@@ -4,7 +4,7 @@ A worktree created off the standalone clone's local ``HEAD`` roots the new
branch on a stale base when that clone lags the remote. ``_resolve_worktree_base``
fetches and branches from the remote tip instead so the worktree starts current.
These tests exercise the REAL ``cli._resolve_worktree_base`` /
These tests exercise the REAL ``worktree_ops._resolve_worktree_base`` /
``cli._setup_worktree`` against a real local "remote" repo (so ``git fetch``
works offline in the hermetic sandbox), proving the worktree includes commits
that exist on the remote but not on the stale local HEAD.
@@ -17,6 +17,8 @@ from pathlib import Path
import pytest
from hermes_cli import worktree_ops
import cli
@@ -76,7 +78,7 @@ def remote_and_clone(tmp_path):
class TestResolveWorktreeBase:
def test_resolves_to_fetched_upstream(self, remote_and_clone):
clone, remote_head, stale_local_head = remote_and_clone
base_ref, label = cli._resolve_worktree_base(str(clone))
base_ref, label = worktree_ops._resolve_worktree_base(str(clone))
# Should resolve to the upstream tracking ref and have fetched it.
assert base_ref == "origin/main"
assert "fetched" in label
@@ -92,7 +94,7 @@ class TestResolveWorktreeBase:
_run(["git", "config", "user.email", "t@t.com"], repo)
_run(["git", "config", "user.name", "T"], repo)
_commit(repo, "README.md", "only commit")
base_ref, label = cli._resolve_worktree_base(str(repo))
base_ref, label = worktree_ops._resolve_worktree_base(str(repo))
assert base_ref == "HEAD"
assert "HEAD" in label
@@ -119,7 +121,7 @@ class TestResolveWorktreeBaseStartupCost:
return real_run(args, **kw)
monkeypatch.setattr(subprocess, "run", spy)
base_ref, label = cli._resolve_worktree_base(str(clone))
base_ref, label = worktree_ops._resolve_worktree_base(str(clone))
assert base_ref == "origin/main"
assert "fetched" in label and "ago" in label
assert calls == [], "fresh FETCH_HEAD must skip the network fetch"
@@ -134,7 +136,7 @@ class TestResolveWorktreeBaseStartupCost:
fetch_head = Path(clone) / ".git" / "FETCH_HEAD"
old = time.time() - 3600
os.utime(fetch_head, (old, old))
base_ref, label = cli._resolve_worktree_base(str(clone))
base_ref, label = worktree_ops._resolve_worktree_base(str(clone))
assert base_ref == "origin/main"
assert label == "origin/main (fetched)"
@@ -154,7 +156,7 @@ class TestResolveWorktreeBaseStartupCost:
monkeypatch.setattr(subprocess, "run", stall_fetches)
start = time.monotonic()
base_ref, label = cli._resolve_worktree_base(str(clone))
base_ref, label = worktree_ops._resolve_worktree_base(str(clone))
elapsed = time.monotonic() - start
# Cached tracking ref, single fetch attempt, no step-2 cascade.
assert base_ref == "origin/main"
@@ -185,7 +187,7 @@ class TestResolveWorktreeBaseStartupCost:
_run(
["git", "config", "branch.main.merge", "refs/heads/main"], repo
)
base_ref, label = cli._resolve_worktree_base(str(repo))
base_ref, label = worktree_ops._resolve_worktree_base(str(repo))
assert base_ref == "HEAD"
assert "HEAD" in label