Feature: LSP Lifecycle Manager coverage Exercise every code path in cleveragents.lsp.lifecycle to reach full branch and line coverage for the LspLifecycleManager and _ManagedServer classes. Background: Given llcov a fresh LspLifecycleManager instance # ---------- start_server: brand-new server (happy path) ---------- Scenario: Start a new LSP server successfully Given llcov a server config named "local/pyright" with command "pyright-langserver" When llcov I start the server for workspace "/tmp/ws" Then llcov the returned client should not be None And llcov the server "local/pyright" should be registered # ---------- start_server: reuse existing alive server ---------- Scenario: Reuse an existing alive server increments ref count Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" When llcov I start the server again for workspace "/tmp/ws" Then llcov the returned client should be the same as the original And llcov the ref count for "local/pyright" should be 2 # ---------- start_server: initialize failure rolls back ---------- Scenario: Start server rolls back transport when initialize fails Given llcov a server config named "local/broken" with command "broken-server" And llcov the LspClient initialize will raise an error When llcov I attempt to start the server for workspace "/tmp/ws" Then llcov a start error should have been raised And llcov the transport should have been stopped after init failure # ---------- start_server: race condition (another thread wins) ---------- Scenario: Start server detects a race condition and reuses the winner Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov another thread already registered "local/pyright" before Phase 3 When llcov I start the server for workspace "/tmp/ws" with race Then llcov the returned client should be the race winner client And llcov the losing transport should have been stopped # ---------- stop_server: decrement but still > 0 ---------- Scenario: Stop server decrements ref count but keeps server alive Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" And llcov the server is started again to bump ref count When llcov I stop the server "local/pyright" Then llcov the server "local/pyright" should still be registered And llcov the ref count for "local/pyright" should be 1 # ---------- stop_server: last reference, full shutdown ---------- Scenario: Stop server shuts down when last reference is released Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" When llcov I stop the server "local/pyright" Then llcov the server "local/pyright" should not be registered And llcov shutdown should have been called on the client # ---------- stop_server: server not found ---------- Scenario: Stop server raises error for unknown server When llcov I attempt to stop server "local/unknown" Then llcov a LspServerNotFoundError should have been raised # ---------- _shutdown_server: graceful shutdown ---------- Scenario: Shutdown server calls client shutdown and transport stop Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" When llcov I trigger shutdown on the managed server Then llcov the client shutdown method should have been called And llcov the transport stop method should have been called # ---------- _shutdown_server: shutdown exception is caught ---------- Scenario: Shutdown server catches exception from client shutdown Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" And llcov the client shutdown will raise an exception When llcov I trigger shutdown on the managed server Then llcov the transport stop method should have been called despite error # ---------- get_client: found ---------- Scenario: Get client returns the client for a running server Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" When llcov I get the client for "local/pyright" Then llcov the returned client should not be None # ---------- get_client: not found ---------- Scenario: Get client raises error for unknown server When llcov I attempt to get the client for "local/missing" Then llcov a LspServerNotFoundError should have been raised # ---------- health_check: server alive ---------- Scenario: Health check returns true for alive server Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" When llcov I health check "local/pyright" Then llcov the health check result should be true # ---------- health_check: server not registered ---------- Scenario: Health check returns false for unknown server When llcov I health check "local/nonexistent" Then llcov the health check result should be false # ---------- restart_server: happy path ---------- Scenario: Restart server stops old transport and creates new client Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" When llcov I restart the server "local/pyright" Then llcov the returned client should not be None And llcov the old transport should have been stopped And llcov the managed server should have the new transport and client # ---------- restart_server: not found ---------- Scenario: Restart server raises error for unknown server When llcov I attempt to restart server "local/ghost" Then llcov a LspServerNotFoundError should have been raised # ---------- restart_server: init failure during restart ---------- Scenario: Restart server stops new transport when re-initialize fails Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" And llcov the LspClient initialize will raise on restart When llcov I attempt to restart server "local/pyright" Then llcov a restart error should have been raised And llcov the new transport should have been stopped after restart failure # ---------- stop_all: multiple servers ---------- Scenario: Stop all shuts down every registered server Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" Given llcov a server config named "local/ruff" with command "ruff-langserver" And llcov the second server is already started for workspace "/tmp/ws2" When llcov I stop all servers Then llcov no servers should be registered # ---------- stop_all: with shutdown error ---------- Scenario: Stop all handles shutdown errors gracefully Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" And llcov the client shutdown will raise an exception When llcov I stop all servers Then llcov no servers should be registered # ---------- stop_all: transport.stop() raises (covers lines 273-277) ---------- Scenario: Stop all catches transport stop error in shutdown Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" And llcov the transport stop will raise an exception When llcov I stop all servers Then llcov no servers should be registered # ---------- list_running: with servers ---------- Scenario: List running returns status for all servers Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" When llcov I list running servers Then llcov the running list should contain 1 entry And llcov the running entry for "local/pyright" should have workspace "/tmp/ws" And llcov the running entry for "local/pyright" should have alive status true And llcov the running entry for "local/pyright" should have ref count 1 # ---------- list_running: empty ---------- Scenario: List running returns empty list when no servers When llcov I list running servers Then llcov the running list should contain 0 entries # ---------- restart_server: 3-phase lock — health_check not blocked ---------- Scenario: health_check is not blocked while restart_server is in progress Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" When llcov I restart the server concurrently and health_check runs during blocking IO Then llcov health_check should have completed without blocking And llcov the restarted client should not be None # ---------- restart_server: 3-phase lock — lock not held during initialize ---------- Scenario: restart_server does not hold the lock during client.initialize Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" When llcov I restart the server and verify lock is free during initialize Then llcov the lock should have been acquirable during initialize And llcov the restarted client should not be None # ---------- restart_server: ref_count preserved after restart ---------- Scenario: restart_server preserves the ref_count of the managed server Given llcov a server config named "local/pyright" with command "pyright-langserver" And llcov the server is already started for workspace "/tmp/ws" And llcov the server is started again to bump ref count When llcov I restart the server "local/pyright" Then llcov the ref count for "local/pyright" should be 2