diff --git a/tests/tui_gateway/test_projects_rpc.py b/tests/tui_gateway/test_projects_rpc.py index cb9638420fc52..e3dcf61a51e61 100644 --- a/tests/tui_gateway/test_projects_rpc.py +++ b/tests/tui_gateway/test_projects_rpc.py @@ -301,6 +301,28 @@ def test_desktop_launch_cwd_is_not_persisted_as_a_workspace(): ) == "/picked/repo" +def test_home_container_dirs_are_never_a_workspace(tmp_path): + """`/home` and `/Users` hold homes; they are not workspaces themselves. + + A session whose cwd is one of them used to be promoted to its own auto + project, so the sidebar showed a second row labelled "home" sitting right + next to the synthetic Home bucket. Both POSIX spellings are excluded on + every host: either can reach a local row (macOS ships an empty `/home` + stub) or arrive from a container/remote shell. + """ + home = os.path.realpath(os.path.expanduser("~")) + + for path in (os.sep, home, os.path.dirname(home), "/home", "/Users"): + assert server._is_session_cwd_junk(path), path + assert server._is_repo_junk(path), path + + # An ordinary directory is still a workspace. + workspace = tmp_path / "a-repo" + workspace.mkdir() + assert not server._is_session_cwd_junk(str(workspace)) + assert not server._is_repo_junk(str(workspace)) + + def test_disabled_discovery_clears_cache_and_rejects_new_scan(monkeypatch, tmp_path): repo = tmp_path / "cached-repo" repo.mkdir() diff --git a/tui_gateway/server.py b/tui_gateway/server.py index c33cba50ea977..1631441bfbfa8 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -11510,20 +11510,41 @@ def _(rid, params, pdb, conn) -> dict: return _ok(rid, {"project": proj.to_dict() if proj else None, "cwd": cwd, "branch": _git_branch_for_cwd(cwd)}) +def _non_workspace_dirs() -> set[str]: + """Directories that are never a workspace, whichever tier proposes them. + + The filesystem root, the user's home, and the directory homes live in — + ``/home`` on Linux, ``/Users`` on macOS, ``C:\\Users`` on Windows. Both + POSIX spellings are excluded on every host because both are reachable as a + cwd anywhere: macOS ships an empty ``/home`` autofs stub, and a container or + remote shell hands back Linux paths. Promoting one of these mints a + catch-all project that swallows unplaced sessions, and ``/home`` in + particular renders as a second row reading "home" next to the Home bucket. + """ + home = os.path.realpath(os.path.expanduser("~")) + candidates = (os.sep, home, os.path.dirname(home), "/home", "/Users") + + return {os.path.normcase(os.path.realpath(path)) for path in candidates if path} + + def _is_repo_junk(root: str) -> bool: - """A git root we never auto-surface as a project: the bare home dir or - anything under HERMES_HOME (~/.hermes by default) — config/sessions/skills, - not a workspace. User-created projects pointing there are still honored.""" + """A git root we never auto-surface as a project: a non-workspace dir (see + :func:`_non_workspace_dirs`) or anything under HERMES_HOME (~/.hermes by + default) — config/sessions/skills, not a workspace. User-created projects + pointing there are still honored.""" if not root: return True from hermes_constants import get_hermes_home real = os.path.realpath(root) - home = os.path.realpath(os.path.expanduser("~")) hermes_home = os.path.realpath(str(get_hermes_home())) - return real == home or real == hermes_home or real.startswith(hermes_home + os.sep) + return ( + os.path.normcase(real) in _non_workspace_dirs() + or real == hermes_home + or real.startswith(hermes_home + os.sep) + ) def _is_session_cwd_junk(cwd: str) -> bool: @@ -11531,8 +11552,9 @@ def _is_session_cwd_junk(cwd: str) -> bool: Unlike discovered git roots, an explicitly selected descendant of HERMES_HOME may be an intentional prose/data workspace. The pre-Projects - desktop surfaced every such cwd, so exclude only the two broad defaults - that would create catch-all projects. + desktop surfaced every such cwd, so exclude only the broad defaults that + would create catch-all projects: HERMES_HOME itself and the dirs in + :func:`_non_workspace_dirs`. """ if not cwd: return True @@ -11540,9 +11562,8 @@ def _is_session_cwd_junk(cwd: str) -> bool: from hermes_constants import get_hermes_home real = os.path.normcase(os.path.realpath(cwd)) - home = os.path.normcase(os.path.realpath(os.path.expanduser("~"))) hermes_home = os.path.normcase(os.path.realpath(str(get_hermes_home()))) - return real == home or real == hermes_home + return real in _non_workspace_dirs() or real == hermes_home def _repo_discovery_policy(raw: dict | None = None) -> dict: