refined-steep-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,317 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Refined::Steep::Server::LspServer do
4
+ include_context "lsp_helpers"
5
+
6
+ describe "initialize request" do
7
+ it "returns server capabilities" do
8
+ messages = [
9
+ {
10
+ id: 1,
11
+ method: "initialize",
12
+ params: {
13
+ rootUri: "file://#{fixtures_dir}",
14
+ capabilities: {},
15
+ },
16
+ },
17
+ ]
18
+
19
+ server, writer = create_server(messages)
20
+ server.start
21
+ sleep 0.1
22
+
23
+ responses = parse_responses(writer)
24
+ init_response = responses.find { |r| r[:id] == 1 }
25
+
26
+ expect(init_response).not_to be_nil
27
+ result = init_response[:result]
28
+ capabilities = result[:capabilities]
29
+
30
+ expect(capabilities[:hoverProvider]).to be true
31
+ expect(capabilities[:definitionProvider]).to be true
32
+ expect(capabilities[:implementationProvider]).to be true
33
+ expect(capabilities[:typeDefinitionProvider]).to be true
34
+ expect(capabilities[:workspaceSymbolProvider]).to be true
35
+ expect(capabilities[:completionProvider]).to include(:triggerCharacters)
36
+ expect(capabilities[:signatureHelpProvider]).to include(:triggerCharacters)
37
+ expect(capabilities[:textDocumentSync][:openClose]).to be true
38
+ expect(result[:serverInfo][:name]).to eq("refined-steep-server")
39
+ end
40
+ end
41
+
42
+ describe "textDocument/didOpen and didClose" do
43
+ it "tracks document lifecycle" do
44
+ uri = "file://#{lib_dir}/test.rb"
45
+ messages = init_messages([
46
+ {
47
+ method: "textDocument/didOpen",
48
+ params: {
49
+ textDocument: { uri: uri, languageId: "ruby", version: 1, text: "class Foo; end" },
50
+ },
51
+ },
52
+ {
53
+ method: "textDocument/didClose",
54
+ params: { textDocument: { uri: uri } },
55
+ },
56
+ ])
57
+
58
+ server, = create_server(messages)
59
+ server.start
60
+ sleep 0.2
61
+
62
+ expect(server.store&.get(uri)).to be_nil
63
+ end
64
+ end
65
+
66
+ describe "textDocument/didChange" do
67
+ it "applies changes through store" do
68
+ uri = "file://#{lib_dir}/test.rb"
69
+ messages = init_messages([
70
+ {
71
+ method: "textDocument/didOpen",
72
+ params: {
73
+ textDocument: { uri: uri, languageId: "ruby", version: 1, text: "class Foo; end" },
74
+ },
75
+ },
76
+ {
77
+ method: "textDocument/didChange",
78
+ params: {
79
+ textDocument: { uri: uri, version: 2 },
80
+ contentChanges: [{ text: "class Bar; end" }],
81
+ },
82
+ },
83
+ ])
84
+
85
+ server, = create_server(messages)
86
+ server.start
87
+ sleep 0.2
88
+
89
+ entry = server.store&.get(uri)
90
+ expect(entry).not_to be_nil
91
+ expect(entry.content).to eq("class Bar; end")
92
+ expect(entry.version).to eq(2)
93
+ end
94
+ end
95
+
96
+ describe "shutdown and exit" do
97
+ it "shuts down cleanly" do
98
+ messages = [
99
+ {
100
+ id: 1,
101
+ method: "initialize",
102
+ params: { rootUri: "file://#{fixtures_dir}", capabilities: {} },
103
+ },
104
+ { id: 2, method: "shutdown", params: {} },
105
+ { method: "exit" },
106
+ ]
107
+
108
+ server, writer = create_server(messages)
109
+
110
+ expect { server.start }.to raise_error(SystemExit) do |error|
111
+ expect(error.status).to eq(0)
112
+ end
113
+
114
+ responses = parse_responses(writer)
115
+ shutdown_response = responses.find { |r| r[:id] == 2 }
116
+ expect(shutdown_response).not_to be_nil
117
+ expect(shutdown_response[:result]).to be_nil
118
+ end
119
+ end
120
+
121
+ describe "Steepfile discovery" do
122
+ it "finds Steepfile from rootUri" do
123
+ messages = [
124
+ {
125
+ id: 1,
126
+ method: "initialize",
127
+ params: { rootUri: "file://#{fixtures_dir}", capabilities: {} },
128
+ },
129
+ ]
130
+
131
+ server, = create_server(messages)
132
+ server.start
133
+ sleep 0.1
134
+
135
+ expect(server.steep_state).not_to be_nil
136
+ expect(server.steep_state.project.targets.size).to eq(1)
137
+ end
138
+ end
139
+
140
+ context "with typed project" do
141
+ let(:source_code) do
142
+ <<~RUBY
143
+ class Greeter
144
+ attr_reader :name
145
+
146
+ def initialize(name)
147
+ @name = name
148
+ end
149
+
150
+ def greet
151
+ "Hello, " + name
152
+ end
153
+ end
154
+ RUBY
155
+ end
156
+
157
+ let(:rbs_content) do
158
+ <<~RBS
159
+ class Greeter
160
+ attr_reader name: String
161
+
162
+ def initialize: (String name) -> void
163
+ def greet: () -> String
164
+ end
165
+ RBS
166
+ end
167
+
168
+ before do
169
+ (lib_dir / "greeter.rb").write(source_code)
170
+ (sig_dir / "greeter.rbs").write(rbs_content)
171
+ end
172
+
173
+ def open_and_request(id:, method:, uri:, text:, request_params:)
174
+ init_messages([
175
+ {
176
+ method: "textDocument/didOpen",
177
+ params: {
178
+ textDocument: { uri: uri, languageId: "ruby", version: 1, text: text },
179
+ },
180
+ },
181
+ { id: id, method: method, params: request_params },
182
+ ])
183
+ end
184
+
185
+ describe "textDocument/completion" do
186
+ it "returns completion items for method calls" do
187
+ uri = "file://#{lib_dir}/greeter.rb"
188
+ completion_source = source_code.sub("\"Hello, \" + name", "self.")
189
+
190
+ messages = open_and_request(
191
+ id: 10,
192
+ method: "textDocument/completion",
193
+ uri: uri,
194
+ text: completion_source,
195
+ request_params: {
196
+ textDocument: { uri: uri },
197
+ position: { line: 8, character: 9 },
198
+ context: { triggerKind: 2, triggerCharacter: "." },
199
+ },
200
+ )
201
+
202
+ server, writer = create_server(messages)
203
+ server.start
204
+ sleep 1.0
205
+
206
+ responses = parse_responses(writer)
207
+ completion_response = responses.find { |r| r[:id] == 10 }
208
+
209
+ expect(completion_response).not_to be_nil
210
+ expect(completion_response[:error]).to be_nil
211
+
212
+ result = completion_response[:result]
213
+ expect(result).to include(:items)
214
+
215
+ items = result[:items]
216
+ labels = items.map { |item| item[:label] }
217
+ expect(labels).to include("name")
218
+ expect(labels).to include("greet")
219
+
220
+ # Completion items should include documentation with type info
221
+ name_item = items.find { |item| item[:label] == "name" }
222
+ expect(name_item[:documentation]).not_to be_nil
223
+ expect(name_item[:documentation][:kind]).to eq("markdown")
224
+
225
+ greet_item = items.find { |item| item[:label] == "greet" }
226
+ expect(greet_item[:documentation]).not_to be_nil
227
+ end
228
+
229
+ it "returns empty completion list when no completions available" do
230
+ uri = "file://#{lib_dir}/greeter.rb"
231
+
232
+ messages = open_and_request(
233
+ id: 10,
234
+ method: "textDocument/completion",
235
+ uri: uri,
236
+ text: source_code,
237
+ request_params: {
238
+ textDocument: { uri: uri },
239
+ position: { line: 0, character: 0 },
240
+ context: { triggerKind: 1 },
241
+ },
242
+ )
243
+
244
+ server, writer = create_server(messages)
245
+ server.start
246
+ sleep 1.0
247
+
248
+ responses = parse_responses(writer)
249
+ completion_response = responses.find { |r| r[:id] == 10 }
250
+
251
+ expect(completion_response).not_to be_nil
252
+ expect(completion_response[:error]).to be_nil
253
+
254
+ result = completion_response[:result]
255
+ expect(result).to include(:items)
256
+ expect(result[:items]).to be_an(Array)
257
+ end
258
+
259
+ it "returns completion response without error for unknown file" do
260
+ unknown_uri = "file://#{lib_dir}/unknown.rb"
261
+
262
+ messages = init_messages([
263
+ {
264
+ id: 10,
265
+ method: "textDocument/completion",
266
+ params: {
267
+ textDocument: { uri: unknown_uri },
268
+ position: { line: 0, character: 0 },
269
+ context: { triggerKind: 1 },
270
+ },
271
+ },
272
+ ])
273
+
274
+ server, writer = create_server(messages)
275
+ server.start
276
+ sleep 0.5
277
+
278
+ responses = parse_responses(writer)
279
+ completion_response = responses.find { |r| r[:id] == 10 }
280
+
281
+ expect(completion_response).not_to be_nil
282
+ if completion_response[:result]
283
+ expect(completion_response[:result][:items]).to be_an(Array)
284
+ else
285
+ expect(completion_response[:result]).to be_nil
286
+ end
287
+ end
288
+ end
289
+
290
+ describe "textDocument/hover" do
291
+ it "returns hover information for typed expressions" do
292
+ uri = "file://#{lib_dir}/greeter.rb"
293
+
294
+ messages = open_and_request(
295
+ id: 10,
296
+ method: "textDocument/hover",
297
+ uri: uri,
298
+ text: source_code,
299
+ request_params: {
300
+ textDocument: { uri: uri },
301
+ position: { line: 3, character: 10 },
302
+ },
303
+ )
304
+
305
+ server, writer = create_server(messages)
306
+ server.start
307
+ sleep 1.0
308
+
309
+ responses = parse_responses(writer)
310
+ hover_response = responses.find { |r| r[:id] == 10 }
311
+
312
+ expect(hover_response).not_to be_nil
313
+ expect(hover_response[:error]).to be_nil
314
+ end
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Refined::Steep::Server do
4
+ describe Refined::Steep::Server::Result do
5
+ it "serializes to hash with id and result" do
6
+ result = Refined::Steep::Server::Result.new(id: 1, response: { key: "value" })
7
+ expect(result.to_hash).to eq({ id: 1, result: { key: "value" } })
8
+ end
9
+
10
+ it "serializes nil response" do
11
+ result = Refined::Steep::Server::Result.new(id: 2, response: nil)
12
+ expect(result.to_hash).to eq({ id: 2, result: nil })
13
+ end
14
+ end
15
+
16
+ describe Refined::Steep::Server::ErrorResponse do
17
+ it "serializes to hash with error structure" do
18
+ error = Refined::Steep::Server::ErrorResponse.new(
19
+ id: 1,
20
+ code: -32600,
21
+ message: "Invalid Request",
22
+ )
23
+ expect(error.to_hash).to eq({
24
+ id: 1,
25
+ error: {
26
+ code: -32600,
27
+ message: "Invalid Request",
28
+ data: nil,
29
+ },
30
+ })
31
+ end
32
+
33
+ it "includes data when provided" do
34
+ error = Refined::Steep::Server::ErrorResponse.new(
35
+ id: 1,
36
+ code: -32600,
37
+ message: "Invalid Request",
38
+ data: { detail: "something" },
39
+ )
40
+ expect(error.to_hash[:error][:data]).to eq({ detail: "something" })
41
+ end
42
+ end
43
+
44
+ describe Refined::Steep::Server::Notification do
45
+ it "serializes to hash with method and params" do
46
+ notification = Refined::Steep::Server::Notification.window_log_message("hello")
47
+ hash = notification.to_hash
48
+ expect(hash[:method]).to eq("window/logMessage")
49
+ expect(hash[:params]).to include(:type, :message)
50
+ end
51
+
52
+ it "creates publish_diagnostics notification" do
53
+ notification = Refined::Steep::Server::Notification.publish_diagnostics(
54
+ "file:///test.rb",
55
+ [],
56
+ )
57
+ hash = notification.to_hash
58
+ expect(hash[:method]).to eq("textDocument/publishDiagnostics")
59
+ expect(hash[:params][:uri]).to eq("file:///test.rb")
60
+ end
61
+ end
62
+
63
+ describe Refined::Steep::Server::Request do
64
+ it "serializes to hash with id, method, and params" do
65
+ params = LanguageServer::Protocol::Interface::ShowMessageParams.new(
66
+ type: 1,
67
+ message: "test",
68
+ )
69
+ request = Refined::Steep::Server::Request.new(
70
+ id: 5,
71
+ method: "window/showMessage",
72
+ params: params,
73
+ )
74
+ hash = request.to_hash
75
+ expect(hash[:id]).to eq(5)
76
+ expect(hash[:method]).to eq("window/showMessage")
77
+ expect(hash[:params]).to include(:type, :message)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Refined::Steep::Server::SteepState do
4
+ include_context "steep_fixture"
5
+
6
+ describe "#initialize" do
7
+ it "loads the project from Steepfile" do
8
+ state = described_class.new(steepfile_path: steepfile_path)
9
+ expect(state.project).to be_a(::Steep::Project)
10
+ expect(state.project.targets.size).to eq(1)
11
+ expect(state.project.targets[0].name).to eq(:lib)
12
+ end
13
+
14
+ it "creates a TypeCheckService" do
15
+ state = described_class.new(steepfile_path: steepfile_path)
16
+ expect(state.type_check_service).to be_a(::Steep::Services::TypeCheckService)
17
+ end
18
+ end
19
+
20
+ describe "#push_changes and #flush_changes" do
21
+ it "buffers and flushes changes" do
22
+ state = described_class.new(steepfile_path: steepfile_path)
23
+ path = Pathname("lib/test.rb")
24
+ change = ::Steep::Services::ContentChange.new(text: "class Foo; end")
25
+
26
+ state.push_changes(path, [change])
27
+ flushed = state.flush_changes
28
+
29
+ expect(flushed.keys).to eq([path])
30
+ expect(flushed[path].size).to eq(1)
31
+ expect(flushed[path][0].text).to eq("class Foo; end")
32
+ end
33
+
34
+ it "clears buffer after flush" do
35
+ state = described_class.new(steepfile_path: steepfile_path)
36
+ path = Pathname("lib/test.rb")
37
+ change = ::Steep::Services::ContentChange.new(text: "class Foo; end")
38
+
39
+ state.push_changes(path, [change])
40
+ state.flush_changes
41
+ flushed = state.flush_changes
42
+
43
+ expect(flushed).to be_empty
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Refined::Steep::Server::Store do
4
+ include_context "steep_fixture"
5
+
6
+ let(:steep_state) { Refined::Steep::Server::SteepState.new(steepfile_path: steepfile_path) }
7
+ let(:store) { described_class.new(steep_state) }
8
+
9
+ describe "#open" do
10
+ it "stores the document" do
11
+ uri = "file://#{fixtures_dir}/lib/test.rb"
12
+ store.open(uri: uri, text: "class Foo; end", version: 1)
13
+
14
+ entry = store.get(uri)
15
+ expect(entry).not_to be_nil
16
+ expect(entry.content).to eq("class Foo; end")
17
+ expect(entry.version).to eq(1)
18
+ end
19
+
20
+ it "pushes changes to steep_state" do
21
+ uri = "file://#{fixtures_dir}/lib/test.rb"
22
+ store.open(uri: uri, text: "class Foo; end", version: 1)
23
+
24
+ changes = steep_state.flush_changes
25
+ expect(changes).not_to be_empty
26
+ end
27
+ end
28
+
29
+ describe "#change" do
30
+ it "pushes incremental changes to steep_state" do
31
+ uri = "file://#{fixtures_dir}/lib/test.rb"
32
+ store.open(uri: uri, text: "class Foo; end", version: 1)
33
+ steep_state.flush_changes
34
+
35
+ store.change(
36
+ uri: uri,
37
+ content_changes: [
38
+ {
39
+ range: {
40
+ start: { line: 0, character: 6 },
41
+ end: { line: 0, character: 9 },
42
+ },
43
+ text: "Bar",
44
+ },
45
+ ],
46
+ version: 2,
47
+ )
48
+
49
+ changes = steep_state.flush_changes
50
+ expect(changes).not_to be_empty
51
+ path = changes.keys.first
52
+ expect(changes[path].first.text).to eq("Bar")
53
+ expect(changes[path].first.range).not_to be_nil
54
+ end
55
+
56
+ it "pushes full text changes to steep_state" do
57
+ uri = "file://#{fixtures_dir}/lib/test.rb"
58
+ store.open(uri: uri, text: "class Foo; end", version: 1)
59
+ steep_state.flush_changes
60
+
61
+ store.change(
62
+ uri: uri,
63
+ content_changes: [{ text: "class Bar; end" }],
64
+ version: 2,
65
+ )
66
+
67
+ entry = store.get(uri)
68
+ expect(entry.content).to eq("class Bar; end")
69
+ expect(entry.version).to eq(2)
70
+ end
71
+ end
72
+
73
+ describe "#close" do
74
+ it "removes the document" do
75
+ uri = "file://#{fixtures_dir}/lib/test.rb"
76
+ store.open(uri: uri, text: "class Foo; end", version: 1)
77
+ store.close(uri: uri)
78
+
79
+ expect(store.get(uri)).to be_nil
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refined/steep/server"
4
+
5
+ RSpec.configure do |config|
6
+ config.expect_with :rspec do |expectations|
7
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
8
+ end
9
+
10
+ config.mock_with :rspec do |mocks|
11
+ mocks.verify_partial_doubles = true
12
+ end
13
+
14
+ config.shared_context_metadata_behavior = :apply_to_host_groups
15
+ config.filter_run_when_matching :focus
16
+ config.order = :random
17
+ end
18
+
19
+ RSpec.shared_context "steep_fixture" do
20
+ let(:fixtures_dir) { Pathname(File.expand_path("../fixtures", __dir__)) }
21
+ let(:steepfile_path) { fixtures_dir / "Steepfile" }
22
+ let(:lib_dir) { fixtures_dir / "lib" }
23
+ let(:sig_dir) { fixtures_dir / "sig" }
24
+
25
+ before do
26
+ FileUtils.mkdir_p(lib_dir)
27
+ FileUtils.mkdir_p(sig_dir)
28
+ steepfile_path.write(<<~RUBY)
29
+ target :lib do
30
+ check "lib"
31
+ signature "sig"
32
+ end
33
+ RUBY
34
+ end
35
+
36
+ after do
37
+ FileUtils.rm_rf(fixtures_dir)
38
+ end
39
+ end
40
+
41
+ RSpec.shared_context "lsp_helpers" do
42
+ include_context "steep_fixture"
43
+
44
+ let(:logger) { Refined::Steep::Server::BaseServer.create_default_logger(level: Logger::WARN, io: StringIO.new) }
45
+
46
+ def encode_message(hash)
47
+ json = JSON.generate(hash)
48
+ "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
49
+ end
50
+
51
+ def create_server(messages)
52
+ raw = messages.map { |m| encode_message(m) }.join
53
+ reader = StringIO.new(raw)
54
+ writer = StringIO.new
55
+
56
+ server = Refined::Steep::Server::LspServer.new(reader: reader, writer: writer, logger: logger)
57
+ [server, writer]
58
+ end
59
+
60
+ def parse_responses(writer)
61
+ output = writer.string
62
+ responses = []
63
+ scanner = StringIO.new(output)
64
+ while (headers = scanner.gets("\r\n\r\n"))
65
+ content_length = headers[/Content-Length: (\d+)/i, 1]&.to_i
66
+ next unless content_length
67
+
68
+ raw = scanner.read(content_length)
69
+ next unless raw
70
+
71
+ responses << JSON.parse(raw, symbolize_names: true)
72
+ end
73
+ responses
74
+ end
75
+
76
+ def init_messages(extras = [])
77
+ [
78
+ {
79
+ id: 1,
80
+ method: "initialize",
81
+ params: { rootUri: "file://#{fixtures_dir}", capabilities: {} },
82
+ },
83
+ { method: "initialized", params: {} },
84
+ *extras,
85
+ ]
86
+ end
87
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: refined-steep-server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - joker1007
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: steep
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.10'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.10'
26
+ - !ruby/object:Gem::Dependency
27
+ name: language_server-protocol
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.17'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.17'
40
+ description: Language server implementation that provides Steep-equivalent type checking
41
+ features using ruby-lsp's single-process multi-threaded architecture
42
+ email:
43
+ - kakyoin.hierophant@gmail.com
44
+ executables:
45
+ - refined-steep-server
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".claude/commands/add-stub.md"
50
+ - ".claude/commands/check.md"
51
+ - ".github/workflows/ci.yml"
52
+ - ".rspec"
53
+ - CLAUDE.md
54
+ - README.md
55
+ - Rakefile
56
+ - Steepfile
57
+ - exe/refined-steep-server
58
+ - lib/refined/steep/server.rb
59
+ - lib/refined/steep/server/base_server.rb
60
+ - lib/refined/steep/server/io.rb
61
+ - lib/refined/steep/server/lsp_server.rb
62
+ - lib/refined/steep/server/message.rb
63
+ - lib/refined/steep/server/steep_state.rb
64
+ - lib/refined/steep/server/store.rb
65
+ - lib/refined/steep/server/version.rb
66
+ - rbs_collection.yaml
67
+ - sig/external/steep.rbs
68
+ - sig/refined/steep/server.rbs
69
+ - spec/refined/steep/server/base_server_spec.rb
70
+ - spec/refined/steep/server/io_spec.rb
71
+ - spec/refined/steep/server/lsp_server_spec.rb
72
+ - spec/refined/steep/server/message_spec.rb
73
+ - spec/refined/steep/server/steep_state_spec.rb
74
+ - spec/refined/steep/server/store_spec.rb
75
+ - spec/spec_helper.rb
76
+ homepage: https://github.com/joker1007/refined-steep-server
77
+ licenses: []
78
+ metadata:
79
+ homepage_uri: https://github.com/joker1007/refined-steep-server
80
+ source_code_uri: https://github.com/joker1007/refined-steep-server
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 3.2.0
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 4.0.6
96
+ specification_version: 4
97
+ summary: A language server using Steep as a library with ruby-lsp architecture
98
+ test_files: []