diff --git a/src/session_recall/providers/copilot_cli/_labels.py b/src/session_recall/providers/copilot_cli/_labels.py index 4da82b3..343fa41 100644 --- a/src/session_recall/providers/copilot_cli/_labels.py +++ b/src/session_recall/providers/copilot_cli/_labels.py @@ -2,12 +2,36 @@ from __future__ import annotations +import ntpath +import posixpath from pathlib import Path from ...util.detect_repo import detect_repo_for_cwd +def _is_posixish_path(path_str: str) -> bool: + """Return True for POSIX/WSL-style paths even when running on Windows.""" + return path_str.startswith(("/", "~/")) + + def _detect_repo_for_path(path_str: str) -> str | None: + """Detect a repository for a path without rewriting POSIX paths on Windows. + + Copilot CLI state can contain Linux/WSL-style paths even when this package is + executed by Windows Python. Passing those strings through pathlib.Path on + Windows rewrites separators (``/work`` -> ``\\work``), which breaks repo + detection and changes user-facing local workspace labels. + """ + if _is_posixish_path(path_str): + expanded = posixpath.expanduser(path_str) + for candidate in (expanded, posixpath.dirname(expanded)): + if not candidate: + continue + repo = detect_repo_for_cwd(candidate) + if repo: + return repo + return None + path = Path(path_str).expanduser() candidate = path if path.is_dir() else path.parent return detect_repo_for_cwd(str(candidate)) @@ -16,5 +40,6 @@ def _detect_repo_for_path(path_str: str) -> str | None: def _local_workspace_label(path_str: str | None) -> str | None: if not path_str: return None - expanded = Path(path_str).expanduser() - return f"local:{expanded}" + if _is_posixish_path(path_str): + return f"local:{posixpath.expanduser(path_str)}" + return f"local:{ntpath.normpath(str(Path(path_str).expanduser()))}" diff --git a/src/session_recall/tests/test_health_schema_multistorage.py b/src/session_recall/tests/test_health_schema_multistorage.py index de80403..57c9a2d 100644 --- a/src/session_recall/tests/test_health_schema_multistorage.py +++ b/src/session_recall/tests/test_health_schema_multistorage.py @@ -82,6 +82,64 @@ def test_health_provider_fallback_mode_json(monkeypatch, capsys): assert "Provider:vscode" in names +def test_health_discovers_cli_session_state_when_sqlite_db_is_missing( + monkeypatch, capsys, tmp_path +): + """Regression for issue #19: health should not require session-store.db. + + Newer Copilot CLI layouts may expose ~/.copilot/session-state/*/events.jsonl + without the legacy SQLite session-store.db file. + """ + from session_recall.commands import health + from session_recall.providers import discovery + + state_root = tmp_path / "session-state" + session_dir = state_root / "abcd1234-0000-0000-0000-000000000000" + session_dir.mkdir(parents=True) + (session_dir / "events.jsonl").write_text( + "\n".join( + [ + json.dumps( + { + "type": "session.start", + "data": { + "sessionId": "abcd1234-0000-0000-0000-000000000000", + "context": {"repository": "owner/repo"}, + }, + "timestamp": "2026-04-22T10:00:00.000Z", + } + ), + json.dumps( + { + "type": "user.message", + "data": {"content": "Investigate auth timeout"}, + "timestamp": "2026-04-22T10:01:00.000Z", + } + ), + ] + ) + + "\n", + encoding="utf-8", + ) + + missing_db = str(tmp_path / ".copilot" / "session-store.db") + monkeypatch.setattr(health, "DB_PATH", missing_db) + monkeypatch.setattr(discovery, "CLI_SESSION_STATE_ROOT", str(state_root)) + + args = argparse.Namespace(json=True, provider="all") + rc = health.run(args) + + captured = capsys.readouterr() + assert rc == 0 + assert "database not found" not in captured.err + out = json.loads(captured.out) + assert out["storage_mode"] == "provider-fallback" + assert out["providers"]["cli"]["available"] is True + names = [d["name"] for d in out["dims"]] + assert "SQLite Health Core" in names + assert "Provider:cli" in names + + # ── helpers for integration tests ────────────────────────────────────