diff --git a/src/autoskillit/cli/app.py b/src/autoskillit/cli/app.py index cc831fd37e..ed126bc03b 100644 --- a/src/autoskillit/cli/app.py +++ b/src/autoskillit/cli/app.py @@ -31,7 +31,6 @@ from autoskillit.core import ( RecipeSource, atomic_write, - claude_code_project_dir, ) from autoskillit.execution import _has_active_execution_marker @@ -152,11 +151,11 @@ def serve(*, verbose: Annotated[bool, Parameter(name=["--verbose", "-v"])] = Fal try: backend_options = {"use_uvloop": sys.platform != "win32"} - _marker_dir: Path | None = None - try: - _marker_dir = claude_code_project_dir(str(ctx.project_dir)) - except OSError: - pass + _marker_dir: Path | None = ( + ctx.backend.session_locator().project_log_dir(str(ctx.project_dir)) + if ctx.backend is not None + else None + ) async def _guarded_serve() -> None: await serve_with_signal_guard( diff --git a/tests/cli/test_app_main.py b/tests/cli/test_app_main.py index 6f3e0ed1e7..8b9b950314 100644 --- a/tests/cli/test_app_main.py +++ b/tests/cli/test_app_main.py @@ -2,12 +2,15 @@ from __future__ import annotations +import asyncio import importlib import sys +from pathlib import Path +from unittest.mock import MagicMock import pytest -pytestmark = [pytest.mark.layer("cli"), pytest.mark.small] +pytestmark = [pytest.mark.layer("cli"), pytest.mark.medium] def test_main_does_not_call_app_after_update_restart(monkeypatch: pytest.MonkeyPatch) -> None: @@ -36,3 +39,64 @@ def fake_run_update_checks(**kwargs: object) -> None: pass assert not app_called, "app() must not be called when update path exits the process" + + +def test_serve_activity_check_uses_backend_derived_marker_dir( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + """serve() must derive _marker_dir via backend.session_locator().project_log_dir().""" + app_module = importlib.import_module("autoskillit.cli.app") + + monkeypatch.chdir(tmp_path) + + expected_dir = tmp_path / "backend-marker" + + mock_backend = MagicMock() + mock_backend.session_locator.return_value.project_log_dir.return_value = expected_dir + + mock_ctx = MagicMock() + mock_ctx.project_dir = tmp_path + mock_ctx.backend = mock_backend + mock_ctx.fleet_lock = None + + mock_cfg = MagicMock() + mock_cfg.logging.level = "INFO" + mock_cfg.logging.json_output = None + mock_cfg.safety.protected_branches = [] + + monkeypatch.setattr("autoskillit.config.load_config", lambda _: mock_cfg) + monkeypatch.setattr("autoskillit.core.configure_logging", lambda **kw: None) + monkeypatch.setattr("autoskillit.server.make_context", lambda *a, **kw: mock_ctx) + monkeypatch.setattr("autoskillit.server._initialize", lambda ctx: None) + + captured_activity_check: list = [] + + async def fake_serve_guard(mcp_instance, *, activity_check=None, **kw): + captured_activity_check.append(activity_check) + + monkeypatch.setattr(app_module, "serve_with_signal_guard", fake_serve_guard) + monkeypatch.setattr("anyio.run", lambda fn, **kw: asyncio.run(fn())) + + from autoskillit.cli.app import serve + + serve() + + mock_backend.session_locator.return_value.project_log_dir.assert_called_once_with( + str(tmp_path) + ) + + assert len(captured_activity_check) == 1, "serve_with_signal_guard was not called exactly once" + + seen_marker_dirs: list = [] + monkeypatch.setattr( + app_module, + "is_server_active", + lambda md, fl: (seen_marker_dirs.append(md), False)[-1], + ) + + captured_activity_check[0]() + assert seen_marker_dirs == [expected_dir], ( + f"activity_check should pass backend-derived marker_dir ({expected_dir}), " + f"got {seen_marker_dirs}" + )