@phase2 @domain @lsp @lsp_server_stub Feature: LSP server stub JSON-RPC protocol As a developer integrating LSP into CleverAgents I want a minimal LSP server stub that handles core lifecycle methods So that I can test the JSON-RPC transport and protocol handshake Background: Given a mock LSP transport # --------------------------------------------------------------------------- # Initialize handshake # --------------------------------------------------------------------------- @lsp_initialize Scenario: LSP initialize returns server info and capabilities Given an initialize request with id 1 When the LSP server processes all messages Then the response for id 1 should have result And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub" And the result for id 1 should contain serverInfo with version "0.1.0" And the result for id 1 should contain capabilities key "textDocumentSync" @lsp_initialize Scenario: LSP initialize with client info is accepted Given an initialize request with id 10 and clientInfo name "test-client" When the LSP server processes all messages Then the response for id 10 should have result And the result for id 10 should contain serverInfo with name "cleveragents-lsp-stub" @lsp_initialize Scenario: Initialized notification is silently accepted Given an initialize request with id 1 And a notification for method "initialized" And an exit notification When the LSP server processes all messages Then the response for id 1 should have result And there should be no error responses @lsp_initialize Scenario: Initialized notification before initialize is silently ignored Given a notification for method "initialized" And an exit notification When the LSP server processes all messages Then there should be no error responses And the server exit code should be 1 # --------------------------------------------------------------------------- # Shutdown handshake # --------------------------------------------------------------------------- @lsp_shutdown Scenario: LSP shutdown returns null result Given an initialize request with id 1 And a shutdown request with id 2 And an exit notification When the LSP server processes all messages Then the response for id 2 should have null result And the server exit code should be 0 @lsp_shutdown Scenario: LSP exit without prior shutdown returns exit code 1 Given an initialize request with id 1 And an exit notification When the LSP server processes all messages Then the server exit code should be 1 # --------------------------------------------------------------------------- # Unsupported methods # --------------------------------------------------------------------------- @lsp_unsupported Scenario: Unsupported method after initialize returns MethodNotFound error Given an initialize request with id 1 And a request for method "textDocument/completion" with id 5 And an exit notification When the LSP server processes all messages Then the response for id 5 should have error code -32601 And the error message for id 5 should contain "Not implemented: textDocument/completion" @lsp_unsupported Scenario: Unsupported method textDocument/hover after initialize returns error Given an initialize request with id 1 And a request for method "textDocument/hover" with id 6 And an exit notification When the LSP server processes all messages Then the response for id 6 should have error code -32601 And the error message for id 6 should contain "Not implemented: textDocument/hover" @lsp_unsupported Scenario: Unsupported method before initialize returns ServerNotInitialized Given a request for method "textDocument/completion" with id 5 And an exit notification When the LSP server processes all messages Then the response for id 5 should have error code -32002 And the error message for id 5 should contain "Server not initialized" @lsp_unsupported Scenario: Unsupported notification is silently ignored Given a notification for method "textDocument/didOpen" And an exit notification When the LSP server processes all messages Then there should be no error responses # --------------------------------------------------------------------------- # Invalid JSON-RPC messages # --------------------------------------------------------------------------- @lsp_invalid Scenario: Invalid JSON returns parse error response Given raw bytes that are not valid JSON-RPC And an exit notification When the LSP server processes all messages Then a parse error response should have been sent And the server should have stopped @lsp_invalid Scenario: Missing jsonrpc version field returns error Given a message with missing jsonrpc version and id 7 When the LSP server processes all messages Then the response for id 7 should have error code -32600 And the error message for id 7 should contain "jsonrpc version" @lsp_invalid Scenario: Missing method field returns error Given a message with missing method and id 8 When the LSP server processes all messages Then the response for id 8 should have error code -32600 And the error message for id 8 should contain "method" # --------------------------------------------------------------------------- # Server lifecycle # --------------------------------------------------------------------------- @lsp_lifecycle Scenario: Server startup reports PID Given an initialize request with id 1 And an exit notification When the LSP server processes all messages Then the server should have logged its PID @lsp_lifecycle Scenario: Server handles EOF gracefully When the LSP server processes all messages with empty input Then the server should have stopped And the server exit code should be 1 # --------------------------------------------------------------------------- # A2A facade wiring # --------------------------------------------------------------------------- @lsp_a2a Scenario: LSP server stores provided A2A facade Given an LSP server with A2A facade And an initialize request with id 1 When the LSP server processes all messages Then the response for id 1 should have result And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub" And the server should hold the provided A2A facade @lsp_a2a Scenario: LSP server lazily creates A2A facade on property access Given an initialize request with id 1 And an exit notification When the LSP server processes all messages Then the response for id 1 should have result And the server facade should be lazily created # --------------------------------------------------------------------------- # Full handshake sequence # --------------------------------------------------------------------------- @lsp_full_handshake Scenario: Complete LSP lifecycle (initialize, shutdown, exit) Given an initialize request with id 1 And a shutdown request with id 2 And an exit notification When the LSP server processes all messages Then the response for id 1 should have result And the response for id 2 should have null result And the server exit code should be 0 # --------------------------------------------------------------------------- # Server construction validation # --------------------------------------------------------------------------- @lsp_construction Scenario: LspServer rejects invalid input_stream When I try to create an LspServer with invalid input stream Then a TypeError should be raised for input stream @lsp_construction Scenario: LspServer rejects invalid output_stream When I try to create an LspServer with invalid output stream Then a TypeError should be raised for output stream # --------------------------------------------------------------------------- # Transport edge cases # --------------------------------------------------------------------------- @lsp_transport_edge Scenario: Server recovers from invalid message and handles subsequent request Given a message with missing jsonrpc version and id 7 And an initialize request with id 1 And an exit notification When the LSP server processes all messages Then the response for id 7 should have error code -32600 And the response for id 1 should have result And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub" @lsp_transport_edge Scenario: Request with non-dict params uses empty params Given an initialize request with id 1 And a request with non-dict params and id 20 And an exit notification When the LSP server processes all messages Then the response for id 20 should have error code -32601 @lsp_transport_edge Scenario: Negative content length is handled gracefully Given a message with negative content length When the LSP server processes all messages Then the server should have stopped And the server exit code should be 1 And a transport warning should have been logged for "lsp.transport.negative_content_length" @lsp_transport_edge Scenario: Incomplete message body is handled gracefully Given a message with incomplete body When the LSP server processes all messages Then the server should have stopped And the server exit code should be 1 And a transport warning should have been logged for "lsp.transport.incomplete_read" @lsp_transport_edge Scenario: Unknown method returns MethodNotFound without data field Given an initialize request with id 1 And a request for method "custom/withData" with id 30 And an exit notification When the LSP server processes all messages Then the response for id 30 should have error code -32601 And the error response for id 30 should not contain a data field @lsp_transport_edge Scenario: Missing content length header is handled Given a message with missing content length header When the LSP server processes all messages Then the server should have stopped And the server exit code should be 1 And a transport warning should have been logged for "lsp.transport.missing_content_length" @lsp_transport_edge Scenario: Oversized content length is rejected Given a message with content length exceeding the maximum And an exit notification When the LSP server processes all messages Then the server should have stopped And the server exit code should be 1 And a transport warning should have been logged for "lsp.transport.content_length_exceeded" @lsp_transport_edge Scenario: Too many header lines are rejected Given a message with too many header lines When the LSP server processes all messages Then the server should have stopped And the server exit code should be 1 And a transport warning should have been logged for "lsp.transport.too_many_header_lines" @lsp_transport_edge Scenario: JSON array body returns InvalidRequest error Given a message with a JSON array body And an exit notification When the LSP server processes all messages Then an InvalidRequest error response should have been sent @lsp_transport_edge Scenario: Non-integer content length is handled gracefully Given a message with non-integer content length When the LSP server processes all messages Then the server should have stopped And the server exit code should be 1 And a transport warning should have been logged for "lsp.transport.invalid_content_length" @lsp_transport_edge Scenario: Zero content length returns parse error Given a message with content length zero And an exit notification When the LSP server processes all messages Then a parse error response should have been sent And the server should have stopped @lsp_transport_edge Scenario: Duplicate content-length headers uses last value Given a message with duplicate content-length headers and id 60 And an exit notification When the LSP server processes all messages Then the response for id 60 should have result And the result for id 60 should contain serverInfo with name "cleveragents-lsp-stub" @lsp_transport_edge Scenario: Shutdown without prior initialize returns ServerNotInitialized Given a shutdown request with id 1 And an exit notification When the LSP server processes all messages Then the response for id 1 should have error code -32002 And the error message for id 1 should contain "Server not initialized" @lsp_transport_edge Scenario: Initialize without params key uses empty params Given a message with initialize request without params key and id 50 And an exit notification When the LSP server processes all messages Then the response for id 50 should have result And the result for id 50 should contain serverInfo with name "cleveragents-lsp-stub" # --------------------------------------------------------------------------- # Post-shutdown request rejection (LSP spec ยง3.16) # --------------------------------------------------------------------------- @lsp_shutdown Scenario: Request after shutdown returns InvalidRequest error Given an initialize request with id 1 And a shutdown request with id 2 And a request for method "textDocument/completion" with id 3 And an exit notification When the LSP server processes all messages Then the response for id 3 should have error code -32600 And the error message for id 3 should contain "shutting down" @lsp_shutdown Scenario: Notification after shutdown is silently ignored Given an initialize request with id 1 And a shutdown request with id 2 And a notification for method "textDocument/didOpen" And an exit notification When the LSP server processes all messages Then the response for id 1 should have result And the response for id 2 should have null result And there should be no error responses And the server exit code should be 0 # --------------------------------------------------------------------------- # Double initialize rejection # --------------------------------------------------------------------------- @lsp_initialize Scenario: Second initialize request returns InvalidRequest error Given an initialize request with id 1 And an initialize request with id 2 And an exit notification When the LSP server processes all messages Then the response for id 1 should have result And the response for id 2 should have error code -32600 And the error message for id 2 should contain "already initialized" # --------------------------------------------------------------------------- # Stream desync recovery (transport error followed by valid message) # --------------------------------------------------------------------------- @lsp_transport_edge Scenario: Server recovers after oversized message with body and processes next request Given an oversized message with real body followed by an initialize request with id 1 And an exit notification When the LSP server processes all messages Then the response for id 1 should have result And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub" @lsp_transport_edge Scenario: Server recovers after invalid content-length header and processes next request Given a message with non-integer content length followed by an initialize request with id 1 And an exit notification When the LSP server processes all messages Then the response for id 1 should have result And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub" @lsp_transport_edge Scenario: Server recovers after too many header lines and processes next request Given a message with too many header lines followed by an initialize request with id 1 And an exit notification When the LSP server processes all messages Then the response for id 1 should have result And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub" @lsp_transport_edge Scenario: EOF during body discard of oversized message is handled gracefully Given an oversized message with a short body that triggers EOF during discard When the LSP server processes all messages Then the server should have stopped And the server exit code should be 1 And a transport warning should have been logged for "lsp.transport.content_length_exceeded" @lsp_transport_edge Scenario: Oversized single header line is handled gracefully Given a message with a header line exceeding the maximum line length And an exit notification When the LSP server processes all messages Then the server should have stopped And the server exit code should be 1 And a transport warning should have been logged for "lsp.transport.too_many_header_lines" @lsp_transport_edge Scenario: Deeply nested JSON triggers parse error response Given a message with deeply nested JSON And an exit notification When the LSP server processes all messages Then a parse error response should have been sent And a transport warning should have been logged for "lsp.transport.json_recursion_error" And the server should have stopped # --------------------------------------------------------------------------- # CLI serve command # --------------------------------------------------------------------------- @lsp_cli_serve Scenario: CLI serve command rejects invalid log level When I run lsp CLI serve with log level "banana" Then the lsp serve CLI should have failed And the lsp serve CLI output should contain "Invalid log level" @lsp_cli_serve Scenario: CLI serve command accepts valid log level and runs When I run lsp CLI serve with piped empty stdin Then the lsp serve CLI should have exited with code 1 And the lsp serve CLI output should contain "CleverAgents LSP Server" @lsp_cli_serve Scenario: CLI serve command with debug log level When I run lsp CLI serve with log level "debug" and piped empty stdin Then the lsp serve CLI should have exited with code 1 And the lsp serve CLI output should contain "CleverAgents LSP Server" # --------------------------------------------------------------------------- # Broken output stream (pipe failure) # --------------------------------------------------------------------------- @lsp_transport_edge Scenario: Broken output stream stops the server gracefully Given an initialize request with id 1 And an exit notification When the LSP server processes all messages with a broken output stream Then the server should have stopped And the server exit code should be 1 And a transport warning should have been logged for "lsp.transport.write_error" # --------------------------------------------------------------------------- # String-valued JSON-RPC request id # --------------------------------------------------------------------------- @lsp_initialize Scenario: String request id is echoed correctly Given an initialize request with string id "alpha-1" And an exit notification When the LSP server processes all messages Then the response for string id "alpha-1" should have result And the result for string id "alpha-1" should contain serverInfo with name "cleveragents-lsp-stub" # --------------------------------------------------------------------------- # EOF after shutdown without exit notification # --------------------------------------------------------------------------- @lsp_shutdown Scenario: EOF after shutdown without exit returns exit code 0 Given an initialize request with id 1 And a shutdown request with id 2 When the LSP server processes all messages Then the response for id 2 should have null result And the server exit code should be 0 # --------------------------------------------------------------------------- # Per-message read timeout (DoS protection) # --------------------------------------------------------------------------- @lsp_transport_edge @lsp_dos_protection Scenario: Read timeout is configurable via constructor parameter Given an initialize request with id 1 And an exit notification When the LSP server processes all messages with read timeout 60.0 Then the response for id 1 should have result And the result for id 1 should contain serverInfo with name "cleveragents-lsp-stub" And the server exit code should be 1 @lsp_transport_edge @lsp_dos_protection Scenario: MESSAGE_READ_TIMEOUT constant is exported and has correct default value When I check the MESSAGE_READ_TIMEOUT constant Then the MESSAGE_READ_TIMEOUT should be 30 And MESSAGE_READ_TIMEOUT should be importable from lsp server module @lsp_transport_edge @lsp_dos_protection Scenario: Read body times out immediately with read_timeout zero on a real pipe When I invoke read body with timeout 0.0 on an empty pipe expecting 10 bytes Then the read body result should be None And a transport read_timeout warning should be captured @lsp_transport_edge @lsp_dos_protection Scenario: Read body times out when select reports no data on a real pipe When I invoke read body with timeout 0.05 on an empty pipe expecting 10 bytes Then the read body result should be None And a transport read_timeout warning should be captured