ruboty-ai_agent 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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +45 -0
- data/AGENTS.md +22 -0
- data/CHANGELOG.md +3 -0
- data/CLAUDE.md +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +14 -0
- data/README.md +118 -0
- data/Rakefile +47 -0
- data/Steepfile +12 -0
- data/bin/console +15 -0
- data/bin/ruboty +34 -0
- data/bin/setup +8 -0
- data/lib/ruboty/ai_agent/actions/add_ai_command.rb +22 -0
- data/lib/ruboty/ai_agent/actions/add_ai_memory.rb +20 -0
- data/lib/ruboty/ai_agent/actions/add_mcp.rb +94 -0
- data/lib/ruboty/ai_agent/actions/base.rb +43 -0
- data/lib/ruboty/ai_agent/actions/chat.rb +64 -0
- data/lib/ruboty/ai_agent/actions/list_ai_commands.rb +19 -0
- data/lib/ruboty/ai_agent/actions/list_ai_memories.rb +18 -0
- data/lib/ruboty/ai_agent/actions/list_mcp.rb +18 -0
- data/lib/ruboty/ai_agent/actions/remove_ai_command.rb +18 -0
- data/lib/ruboty/ai_agent/actions/remove_ai_memory.rb +25 -0
- data/lib/ruboty/ai_agent/actions/remove_mcp.rb +24 -0
- data/lib/ruboty/ai_agent/actions/set_system_prompt.rb +31 -0
- data/lib/ruboty/ai_agent/actions/show_system_prompt.rb +30 -0
- data/lib/ruboty/ai_agent/actions.rb +22 -0
- data/lib/ruboty/ai_agent/agent.rb +71 -0
- data/lib/ruboty/ai_agent/cached_value.rb +43 -0
- data/lib/ruboty/ai_agent/chat_message.rb +60 -0
- data/lib/ruboty/ai_agent/chat_thread.rb +31 -0
- data/lib/ruboty/ai_agent/chat_thread_associations.rb +34 -0
- data/lib/ruboty/ai_agent/chat_thread_messages.rb +17 -0
- data/lib/ruboty/ai_agent/commands/base.rb +39 -0
- data/lib/ruboty/ai_agent/commands/clear.rb +29 -0
- data/lib/ruboty/ai_agent/commands/compact.rb +80 -0
- data/lib/ruboty/ai_agent/commands/usage.rb +52 -0
- data/lib/ruboty/ai_agent/commands.rb +33 -0
- data/lib/ruboty/ai_agent/database/query_methods.rb +84 -0
- data/lib/ruboty/ai_agent/database.rb +40 -0
- data/lib/ruboty/ai_agent/global_settings.rb +33 -0
- data/lib/ruboty/ai_agent/http_mcp_client.rb +215 -0
- data/lib/ruboty/ai_agent/llm/openai/model.rb +29 -0
- data/lib/ruboty/ai_agent/llm/openai.rb +181 -0
- data/lib/ruboty/ai_agent/llm/response.rb +21 -0
- data/lib/ruboty/ai_agent/llm.rb +11 -0
- data/lib/ruboty/ai_agent/mcp_clients.rb +48 -0
- data/lib/ruboty/ai_agent/mcp_configuration.rb +31 -0
- data/lib/ruboty/ai_agent/record_set.rb +71 -0
- data/lib/ruboty/ai_agent/recordable.rb +116 -0
- data/lib/ruboty/ai_agent/token_usage.rb +45 -0
- data/lib/ruboty/ai_agent/tool.rb +29 -0
- data/lib/ruboty/ai_agent/user.rb +52 -0
- data/lib/ruboty/ai_agent/user_ai_memories.rb +17 -0
- data/lib/ruboty/ai_agent/user_associations.rb +34 -0
- data/lib/ruboty/ai_agent/user_mcp_caches.rb +90 -0
- data/lib/ruboty/ai_agent/user_mcp_client.rb +93 -0
- data/lib/ruboty/ai_agent/user_mcp_configurations.rb +15 -0
- data/lib/ruboty/ai_agent/user_mcp_tools_caches.rb +14 -0
- data/lib/ruboty/ai_agent/version.rb +7 -0
- data/lib/ruboty/ai_agent.rb +40 -0
- data/lib/ruboty/handlers/ai_agent.rb +84 -0
- data/rbs_collection.yaml +23 -0
- data/ruboty-ai_agent.gemspec +49 -0
- data/script/generate-concern-rbs.rb +351 -0
- data/script/generate-data-rbs.rb +250 -0
- data/script/generate-memorized-ivar-rbs.rb +292 -0
- data/sig/generated/ruboty/ai_agent/actions/add_ai_command.rbs +16 -0
- data/sig/generated/ruboty/ai_agent/actions/add_ai_memory.rbs +14 -0
- data/sig/generated/ruboty/ai_agent/actions/add_mcp.rbs +26 -0
- data/sig/generated/ruboty/ai_agent/actions/base.rbs +34 -0
- data/sig/generated/ruboty/ai_agent/actions/chat.rbs +17 -0
- data/sig/generated/ruboty/ai_agent/actions/list_ai_commands.rbs +13 -0
- data/sig/generated/ruboty/ai_agent/actions/list_ai_memories.rbs +12 -0
- data/sig/generated/ruboty/ai_agent/actions/list_mcp.rbs +12 -0
- data/sig/generated/ruboty/ai_agent/actions/remove_ai_command.rbs +14 -0
- data/sig/generated/ruboty/ai_agent/actions/remove_ai_memory.rbs +14 -0
- data/sig/generated/ruboty/ai_agent/actions/remove_mcp.rbs +14 -0
- data/sig/generated/ruboty/ai_agent/actions/set_system_prompt.rbs +16 -0
- data/sig/generated/ruboty/ai_agent/actions/show_system_prompt.rbs +12 -0
- data/sig/generated/ruboty/ai_agent/actions.rbs +9 -0
- data/sig/generated/ruboty/ai_agent/agent.rbs +29 -0
- data/sig/generated/ruboty/ai_agent/cached_value.rbs +28 -0
- data/sig/generated/ruboty/ai_agent/chat_message.rbs +34 -0
- data/sig/generated/ruboty/ai_agent/chat_thread.rbs +22 -0
- data/sig/generated/ruboty/ai_agent/chat_thread_associations.rbs +21 -0
- data/sig/generated/ruboty/ai_agent/chat_thread_messages.rbs +13 -0
- data/sig/generated/ruboty/ai_agent/commands/base.rbs +40 -0
- data/sig/generated/ruboty/ai_agent/commands/clear.rbs +20 -0
- data/sig/generated/ruboty/ai_agent/commands/compact.rbs +30 -0
- data/sig/generated/ruboty/ai_agent/commands/usage.rbs +26 -0
- data/sig/generated/ruboty/ai_agent/commands.rbs +13 -0
- data/sig/generated/ruboty/ai_agent/database/query_methods.rbs +39 -0
- data/sig/generated/ruboty/ai_agent/database.rbs +27 -0
- data/sig/generated/ruboty/ai_agent/global_settings.rbs +23 -0
- data/sig/generated/ruboty/ai_agent/http_mcp_client.rbs +62 -0
- data/sig/generated/ruboty/ai_agent/llm/openai/model.rbs +21 -0
- data/sig/generated/ruboty/ai_agent/llm/openai.rbs +54 -0
- data/sig/generated/ruboty/ai_agent/llm/response.rbs +29 -0
- data/sig/generated/ruboty/ai_agent/llm.rbs +9 -0
- data/sig/generated/ruboty/ai_agent/mcp_clients.rbs +24 -0
- data/sig/generated/ruboty/ai_agent/mcp_configuration.rbs +35 -0
- data/sig/generated/ruboty/ai_agent/record_set.rbs +42 -0
- data/sig/generated/ruboty/ai_agent/recordable.rbs +56 -0
- data/sig/generated/ruboty/ai_agent/token_usage.rbs +30 -0
- data/sig/generated/ruboty/ai_agent/tool.rbs +27 -0
- data/sig/generated/ruboty/ai_agent/user.rbs +35 -0
- data/sig/generated/ruboty/ai_agent/user_ai_memories.rbs +11 -0
- data/sig/generated/ruboty/ai_agent/user_associations.rbs +21 -0
- data/sig/generated/ruboty/ai_agent/user_mcp_caches.rbs +44 -0
- data/sig/generated/ruboty/ai_agent/user_mcp_client.rbs +58 -0
- data/sig/generated/ruboty/ai_agent/user_mcp_configurations.rbs +11 -0
- data/sig/generated/ruboty/ai_agent/user_mcp_tools_caches.rbs +11 -0
- data/sig/generated/ruboty/ai_agent/version.rbs +7 -0
- data/sig/generated/ruboty/ai_agent.rbs +9 -0
- data/sig/generated/ruboty/handlers/ai_agent.rbs +32 -0
- data/sig/generated-by-scripts/concerns.rbs +27 -0
- data/sig/generated-by-scripts/memorized_ivars.rbs +42 -0
- data/sig-lib/event_stream_parser/event_stream_parser.rbs +21 -0
- data/sig-lib/mem/mem.rbs +19 -0
- data/sig-lib/ruboty/ruboty.rbs +421 -0
- metadata +263 -0
@@ -0,0 +1,250 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'prism'
|
5
|
+
require 'rbs'
|
6
|
+
require 'pathname'
|
7
|
+
require 'fileutils'
|
8
|
+
|
9
|
+
# Process Ruby files to generate RBS definitions for Data classes
|
10
|
+
class Processor
|
11
|
+
Source = Data.define(:file, :content, :prism_node)
|
12
|
+
RbsOutput = Data.define(:origin_source, :content)
|
13
|
+
|
14
|
+
def initialize(lib_path: 'lib', output_path: 'sig/generated-by-scripts')
|
15
|
+
@lib_path = Pathname(lib_path)
|
16
|
+
@output_path = Pathname(output_path)
|
17
|
+
@data_classes = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def process
|
21
|
+
rbs_outputs = each_ruby_file.filter_map do |source|
|
22
|
+
DataClassRbsGenerator.new.process(source)
|
23
|
+
end
|
24
|
+
|
25
|
+
generate_rbs_files(rbs_outputs)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def each_ruby_file
|
31
|
+
puts "Scanning Ruby files in #{@lib_path}..."
|
32
|
+
|
33
|
+
@lib_path.glob('**/*.rb').map do |file|
|
34
|
+
puts "Processing #{file}..."
|
35
|
+
content = file.read
|
36
|
+
|
37
|
+
parsed = Prism.parse(content)
|
38
|
+
|
39
|
+
Source.new(file:, content:, prism_node: parsed.value)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_rbs_files(rbs_outputs)
|
44
|
+
return if rbs_outputs.empty?
|
45
|
+
|
46
|
+
puts "\nGenerating RBS files..."
|
47
|
+
FileUtils.mkdir_p(@output_path)
|
48
|
+
|
49
|
+
content = []
|
50
|
+
content << '# Generated RBS definitions'
|
51
|
+
content << '# This file is automatically generated by script/generate-rbs.rb'
|
52
|
+
content << ''
|
53
|
+
content.concat(rbs_outputs.map(&:content))
|
54
|
+
|
55
|
+
rbs_content = content.join("\n")
|
56
|
+
output_file = @output_path / 'data_classes.rbs'
|
57
|
+
|
58
|
+
File.write(output_file, rbs_content)
|
59
|
+
puts "Generated #{output_file}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Generate RBS definitions for Data classes found in Ruby files
|
64
|
+
class DataClassRbsGenerator
|
65
|
+
DataClass = Data.define(:name, :namespace, :full_name, :attributes, :file)
|
66
|
+
|
67
|
+
# @rbs source: Processor::Source
|
68
|
+
# @rbs return: Processor::RbsOutput?
|
69
|
+
def process(source)
|
70
|
+
visitor = Visitor.new(source)
|
71
|
+
source.prism_node.accept(visitor)
|
72
|
+
|
73
|
+
data_classes = visitor.data_classes
|
74
|
+
|
75
|
+
# Remove duplicates by keeping only unique full_name entries
|
76
|
+
data_classes.uniq!(&:full_name)
|
77
|
+
|
78
|
+
return if data_classes.empty?
|
79
|
+
|
80
|
+
puts "Found #{data_classes.size} Data classes:"
|
81
|
+
data_classes.each do |data_class|
|
82
|
+
puts " - #{data_class.full_name} with attributes: #{data_class.attributes.join(', ')}"
|
83
|
+
end
|
84
|
+
|
85
|
+
rbs_content = RbsPrinter.new(data_classes:).generate_rbs_content
|
86
|
+
Processor::RbsOutput.new(origin_source: source, content: rbs_content)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Visitor to traverse the Prism AST and extract Data class information
|
90
|
+
class Visitor < Prism::Visitor
|
91
|
+
attr_reader :source #: Source
|
92
|
+
attr_reader :data_classes #: Array[DataClass]
|
93
|
+
attr_reader :namespace #: Array[Symbol]
|
94
|
+
|
95
|
+
def initialize(source) # rubocop:disable Lint/MissingSuper --- Prism::Visitor has no initialize method
|
96
|
+
@source = source
|
97
|
+
@data_classes = []
|
98
|
+
@namespace = []
|
99
|
+
end
|
100
|
+
|
101
|
+
def visit_class_node(node)
|
102
|
+
class_name = extract_constant_name(node.constant_path)
|
103
|
+
|
104
|
+
if data_class?(node)
|
105
|
+
attributes = extract_data_attributes(node)
|
106
|
+
if attributes
|
107
|
+
@data_classes << DataClass.new(
|
108
|
+
name: class_name,
|
109
|
+
namespace: namespace.dup,
|
110
|
+
full_name: [*namespace, class_name].join('::'),
|
111
|
+
attributes: attributes,
|
112
|
+
file: source.file
|
113
|
+
)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
in_namespace(class_name) { super }
|
118
|
+
end
|
119
|
+
|
120
|
+
def visit_module_node(node)
|
121
|
+
module_name = extract_constant_name(node.constant_path)
|
122
|
+
|
123
|
+
in_namespace(module_name) do
|
124
|
+
super
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def extract_constant_name(constant_path)
|
129
|
+
case constant_path
|
130
|
+
when Prism::ConstantReadNode
|
131
|
+
constant_path.name.to_s
|
132
|
+
when Prism::ConstantPathNode
|
133
|
+
constant_path.name.to_s
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def data_class?(node)
|
138
|
+
return false unless node.superclass
|
139
|
+
|
140
|
+
case node.superclass
|
141
|
+
when Prism::CallNode
|
142
|
+
if node.superclass.receiver.is_a?(Prism::ConstantReadNode) &&
|
143
|
+
node.superclass.receiver.name == :Data &&
|
144
|
+
node.superclass.name == :define
|
145
|
+
return true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
false
|
150
|
+
end
|
151
|
+
|
152
|
+
def extract_data_attributes(node)
|
153
|
+
return nil unless node.superclass.is_a?(Prism::CallNode)
|
154
|
+
return nil unless node.superclass.name == :define
|
155
|
+
|
156
|
+
arguments = node.superclass.arguments
|
157
|
+
return nil unless arguments
|
158
|
+
|
159
|
+
attributes = []
|
160
|
+
arguments.arguments.each do |arg|
|
161
|
+
case arg
|
162
|
+
when Prism::SymbolNode
|
163
|
+
attributes << arg.value.to_s
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
attributes.empty? ? nil : attributes
|
168
|
+
end
|
169
|
+
|
170
|
+
# @rbs new_namespace: String?
|
171
|
+
def in_namespace(new_namespace)
|
172
|
+
if new_namespace
|
173
|
+
@namespace << new_namespace
|
174
|
+
yield.tap { @namespace.pop }
|
175
|
+
else
|
176
|
+
yield
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Print RBS definitions
|
182
|
+
class RbsPrinter
|
183
|
+
attr_reader :data_classes
|
184
|
+
|
185
|
+
def initialize(data_classes:)
|
186
|
+
@data_classes = data_classes
|
187
|
+
end
|
188
|
+
|
189
|
+
def generate_rbs_content #: String
|
190
|
+
content = []
|
191
|
+
|
192
|
+
@data_classes.group_by(&:namespace).each do |namespace, classes|
|
193
|
+
if namespace.empty?
|
194
|
+
classes.each do |data_class|
|
195
|
+
content << generate_class_rbs(data_class, 0)
|
196
|
+
content << ''
|
197
|
+
end
|
198
|
+
else
|
199
|
+
content.concat(generate_namespaced_classes_rbs(namespace, classes, 0))
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
content.join("\n")
|
204
|
+
end
|
205
|
+
|
206
|
+
def generate_namespaced_classes_rbs(namespace, classes, indent_level)
|
207
|
+
content = []
|
208
|
+
|
209
|
+
namespace.each_with_index do |ns, index|
|
210
|
+
content << indent("module #{ns}", level: indent_level + index)
|
211
|
+
end
|
212
|
+
|
213
|
+
classes.each do |data_class|
|
214
|
+
content << generate_class_rbs(data_class, indent_level + namespace.size)
|
215
|
+
content << ''
|
216
|
+
end
|
217
|
+
|
218
|
+
namespace.reverse.each_with_index do |_ns, index|
|
219
|
+
content << indent('end', level: indent_level + namespace.size - index - 1)
|
220
|
+
end
|
221
|
+
|
222
|
+
content << ''
|
223
|
+
content
|
224
|
+
end
|
225
|
+
|
226
|
+
def generate_class_rbs(data_class, indent_level)
|
227
|
+
content = []
|
228
|
+
content << indent("class #{data_class.name} < Data", level: indent_level)
|
229
|
+
|
230
|
+
data_class.attributes.each do |attr|
|
231
|
+
content << indent("def #{attr}: () -> untyped", level: indent_level + 1)
|
232
|
+
end
|
233
|
+
|
234
|
+
content << indent("def initialize: (#{generate_initialize_signature(data_class.attributes)}) -> void", level: indent_level + 1)
|
235
|
+
content << indent('end', level: indent_level)
|
236
|
+
|
237
|
+
content.join("\n")
|
238
|
+
end
|
239
|
+
|
240
|
+
def generate_initialize_signature(attributes)
|
241
|
+
attributes.map { |attr| "#{attr}: untyped" }.join(', ')
|
242
|
+
end
|
243
|
+
|
244
|
+
def indent(string, level:)
|
245
|
+
' ' * level + string
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
Processor.new.process if __FILE__ == $PROGRAM_NAME
|
@@ -0,0 +1,292 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Generate RBS instance variable definitions for memorized methods
|
5
|
+
#
|
6
|
+
# This script detects methods with @rbs %a{memorized} annotation and generates
|
7
|
+
# corresponding instance variable RBS definitions.
|
8
|
+
#
|
9
|
+
# Example Ruby code that this script detects:
|
10
|
+
#
|
11
|
+
# class User
|
12
|
+
# # @rbs %a{memorized}
|
13
|
+
# def profile #: Profile
|
14
|
+
# @profile ||= Profile.find(user_id: id)
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # @rbs %a{memorized}
|
18
|
+
# def settings #: Settings
|
19
|
+
# @settings ||= Settings.new(user: self)
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# This will generate:
|
24
|
+
#
|
25
|
+
# class User
|
26
|
+
# @profile: Profile
|
27
|
+
# @settings: Settings
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# The script:
|
31
|
+
# 1. Looks for methods with @rbs %a{memorized} annotation
|
32
|
+
# 2. Extracts instance variable names from @ivar ||= patterns
|
33
|
+
# 3. Gets return type from method signature comment (#: Type)
|
34
|
+
# 4. Generates RBS definitions for the instance variables
|
35
|
+
|
36
|
+
require 'prism'
|
37
|
+
require 'pathname'
|
38
|
+
require 'fileutils'
|
39
|
+
|
40
|
+
# Generate RBS instance variable definitions for memorized methods
|
41
|
+
class MemorizedIvarRbsGenerator
|
42
|
+
Source = Data.define(:file, :content, :prism_node)
|
43
|
+
MemorizedMethod = Data.define(:class_name, :method_name, :ivar_name, :return_type, :file)
|
44
|
+
|
45
|
+
def initialize(lib_path: 'lib', output_path: 'sig/generated-by-scripts', namespace_filter: nil)
|
46
|
+
@lib_path = Pathname(lib_path)
|
47
|
+
@output_path = Pathname(output_path)
|
48
|
+
@namespace_filter = namespace_filter
|
49
|
+
end
|
50
|
+
|
51
|
+
def process
|
52
|
+
memorized_methods = find_memorized_methods
|
53
|
+
|
54
|
+
if memorized_methods.empty?
|
55
|
+
puts 'No memorized methods found'
|
56
|
+
return
|
57
|
+
end
|
58
|
+
|
59
|
+
puts "Found #{memorized_methods.size} memorized methods:"
|
60
|
+
memorized_methods.each do |method|
|
61
|
+
puts " - #{method.class_name}##{method.method_name} -> @#{method.ivar_name}: #{method.return_type}"
|
62
|
+
end
|
63
|
+
|
64
|
+
generate_rbs_files(memorized_methods)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def find_memorized_methods
|
70
|
+
methods = []
|
71
|
+
|
72
|
+
@lib_path.glob('**/*.rb').each do |file|
|
73
|
+
content = file.read
|
74
|
+
parsed = Prism.parse(content)
|
75
|
+
|
76
|
+
source = Source.new(file: file, content: content, prism_node: parsed.value)
|
77
|
+
visitor = MemorizedMethodVisitor.new(source, namespace_filter: @namespace_filter)
|
78
|
+
parsed.value.accept(visitor)
|
79
|
+
|
80
|
+
methods.concat(visitor.memorized_methods)
|
81
|
+
end
|
82
|
+
|
83
|
+
methods
|
84
|
+
end
|
85
|
+
|
86
|
+
def generate_rbs_files(memorized_methods)
|
87
|
+
puts "\nGenerating RBS files..."
|
88
|
+
FileUtils.mkdir_p(@output_path)
|
89
|
+
|
90
|
+
content = []
|
91
|
+
content << '# Generated RBS definitions for memorized instance variables'
|
92
|
+
content << '# This file is automatically generated by script/generate-memorized-ivar-rbs.rb'
|
93
|
+
content << ''
|
94
|
+
|
95
|
+
# Group by class name
|
96
|
+
grouped = memorized_methods.group_by(&:class_name)
|
97
|
+
|
98
|
+
grouped.each do |class_name, methods|
|
99
|
+
parts = class_name.split('::')
|
100
|
+
indent_level = 0
|
101
|
+
|
102
|
+
# Open namespaces
|
103
|
+
parts[0...-1].each do |part|
|
104
|
+
content << (' ' * indent_level) + "module #{part}"
|
105
|
+
indent_level += 1
|
106
|
+
end
|
107
|
+
|
108
|
+
# Class declaration with instance variables
|
109
|
+
content << (' ' * indent_level) + "class #{parts.last}"
|
110
|
+
|
111
|
+
methods.each do |method|
|
112
|
+
content << (' ' * (indent_level + 1)) + "@#{method.ivar_name}: #{method.return_type}"
|
113
|
+
end
|
114
|
+
|
115
|
+
content << "#{' ' * indent_level}end"
|
116
|
+
|
117
|
+
# Close namespaces
|
118
|
+
(parts.size - 1).times do
|
119
|
+
indent_level -= 1
|
120
|
+
content << "#{' ' * indent_level}end"
|
121
|
+
end
|
122
|
+
|
123
|
+
content << ''
|
124
|
+
end
|
125
|
+
|
126
|
+
output_file = @output_path / 'memorized_ivars.rbs'
|
127
|
+
File.write(output_file, content.join("\n"))
|
128
|
+
puts "Generated #{output_file}"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Visitor to find memorized methods in Ruby source
|
132
|
+
class MemorizedMethodVisitor < Prism::Visitor
|
133
|
+
attr_reader :source, :memorized_methods, :namespace_filter, :namespace
|
134
|
+
|
135
|
+
def initialize(source, namespace_filter: nil)
|
136
|
+
@source = source
|
137
|
+
@namespace_filter = namespace_filter
|
138
|
+
@memorized_methods = []
|
139
|
+
@namespace = []
|
140
|
+
@current_class = nil
|
141
|
+
super()
|
142
|
+
end
|
143
|
+
|
144
|
+
def visit_class_node(node)
|
145
|
+
class_name = extract_constant_name(node.constant_path)
|
146
|
+
|
147
|
+
in_namespace(class_name, is_class: true) do
|
148
|
+
super
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def visit_module_node(node)
|
153
|
+
module_name = extract_constant_name(node.constant_path)
|
154
|
+
|
155
|
+
in_namespace(module_name, is_class: false) do
|
156
|
+
super
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def visit_def_node(node)
|
161
|
+
return unless @current_class
|
162
|
+
|
163
|
+
# Skip if namespace filter is set and doesn't match
|
164
|
+
return if @namespace_filter && !@current_class.start_with?(@namespace_filter)
|
165
|
+
|
166
|
+
# Check for memorized annotation
|
167
|
+
if memorized_annotated?(node)
|
168
|
+
method_name = node.name.to_s
|
169
|
+
|
170
|
+
# Extract instance variable name and return type
|
171
|
+
ivar_info = extract_ivar_and_type(node)
|
172
|
+
|
173
|
+
if ivar_info
|
174
|
+
@memorized_methods << MemorizedIvarRbsGenerator::MemorizedMethod.new(
|
175
|
+
class_name: @current_class,
|
176
|
+
method_name: method_name,
|
177
|
+
ivar_name: ivar_info[:ivar_name],
|
178
|
+
return_type: ivar_info[:return_type],
|
179
|
+
file: source.file
|
180
|
+
)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
super
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def extract_constant_name(constant_path)
|
190
|
+
case constant_path
|
191
|
+
when Prism::ConstantReadNode
|
192
|
+
constant_path.name.to_s
|
193
|
+
when Prism::ConstantPathNode
|
194
|
+
constant_path.name.to_s
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def in_namespace(name, is_class: false)
|
199
|
+
if name
|
200
|
+
@namespace << name
|
201
|
+
old_class = @current_class
|
202
|
+
@current_class = @namespace.join('::') if is_class
|
203
|
+
yield
|
204
|
+
@current_class = old_class
|
205
|
+
@namespace.pop
|
206
|
+
else
|
207
|
+
yield
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def memorized_annotated?(node)
|
212
|
+
return false unless node.location
|
213
|
+
|
214
|
+
lines = source.content.lines
|
215
|
+
method_line = node.location.start_line
|
216
|
+
|
217
|
+
# Check the line before the method definition
|
218
|
+
return false if method_line <= 1
|
219
|
+
|
220
|
+
comment_line = lines[method_line - 2] # -2 because line numbers are 1-based
|
221
|
+
return false unless comment_line
|
222
|
+
|
223
|
+
comment_line.include?('@rbs %a{memorized}') || comment_line.include?('@rbs %a{ memorized }')
|
224
|
+
end
|
225
|
+
|
226
|
+
def extract_ivar_and_type(node)
|
227
|
+
return nil unless node.body
|
228
|
+
|
229
|
+
# Find instance variable assignments in the method body
|
230
|
+
ivar_name = find_ivar_assignment(node.body)
|
231
|
+
return nil unless ivar_name
|
232
|
+
|
233
|
+
# Extract return type from the method signature comment if present
|
234
|
+
return_type = extract_return_type_from_comment(node)
|
235
|
+
|
236
|
+
{
|
237
|
+
ivar_name: ivar_name,
|
238
|
+
return_type: return_type || 'untyped'
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
def find_ivar_assignment(node, depth = 0)
|
243
|
+
case node
|
244
|
+
when Prism::StatementsNode
|
245
|
+
# Look through all statements
|
246
|
+
node.body.each do |statement|
|
247
|
+
result = find_ivar_assignment(statement, depth + 1)
|
248
|
+
return result if result
|
249
|
+
end
|
250
|
+
nil
|
251
|
+
when Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode
|
252
|
+
# This handles @ivar ||= ...
|
253
|
+
node.name.to_s.delete('@')
|
254
|
+
when Prism::InstanceVariableWriteNode
|
255
|
+
# This handles @ivar = ...
|
256
|
+
node.name.to_s.delete('@')
|
257
|
+
else
|
258
|
+
# Recursively search in child nodes
|
259
|
+
if node.respond_to?(:compact_child_nodes)
|
260
|
+
node.compact_child_nodes.each do |child|
|
261
|
+
result = find_ivar_assignment(child, depth + 1)
|
262
|
+
return result if result
|
263
|
+
end
|
264
|
+
end
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def extract_return_type_from_comment(node)
|
270
|
+
lines = source.content.lines
|
271
|
+
method_line = node.location.start_line
|
272
|
+
|
273
|
+
# Look for the method signature in the same line as def
|
274
|
+
# Pattern: #: ReturnType or #: (...) -> ReturnType
|
275
|
+
line = lines[method_line - 1] # -1 because line numbers are 1-based
|
276
|
+
return nil unless line
|
277
|
+
|
278
|
+
# Match patterns like "#: ChatThreadMessages" or "#: () -> ChatThreadMessages"
|
279
|
+
if (match = line.match(/#:\s*(?:\([^)]*\)\s*->\s*)?(.+)/))
|
280
|
+
return match[1].strip
|
281
|
+
end
|
282
|
+
|
283
|
+
nil
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Run if executed directly
|
289
|
+
if __FILE__ == $PROGRAM_NAME
|
290
|
+
generator = MemorizedIvarRbsGenerator.new(namespace_filter: 'Ruboty')
|
291
|
+
generator.process
|
292
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/actions/add_ai_command.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module Actions
|
6
|
+
# AddAiCommand action for Ruboty::AiAgent
|
7
|
+
class AddAiCommand < Base
|
8
|
+
def call: () -> untyped
|
9
|
+
|
10
|
+
def name_param: () -> String
|
11
|
+
|
12
|
+
def prompt_param: () -> String
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/actions/add_ai_memory.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module Actions
|
6
|
+
# AddAiMemory action for Ruboty::AiAgent
|
7
|
+
class AddAiMemory < Base
|
8
|
+
def call: () -> untyped
|
9
|
+
|
10
|
+
def prompt_param: () -> String
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/actions/add_mcp.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module Actions
|
6
|
+
# AddMcp action for Ruboty::AiAgent
|
7
|
+
class AddMcp < Base
|
8
|
+
def call: () -> untyped
|
9
|
+
|
10
|
+
def name_param: () -> String
|
11
|
+
|
12
|
+
def config_param: () -> String
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
type config = { transport: :http | :sse, headers: Hash[String, String], args: Array[String] }
|
17
|
+
|
18
|
+
def parse_config: () -> config
|
19
|
+
|
20
|
+
# @rbs str: String
|
21
|
+
# @rbs return: String
|
22
|
+
def undump_string: (String str) -> String
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/actions/base.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module Actions
|
6
|
+
# Base class for actions.
|
7
|
+
# @abstract
|
8
|
+
class Base
|
9
|
+
attr_reader message: Ruboty::Message
|
10
|
+
|
11
|
+
# @rbs message: Ruboty::Message
|
12
|
+
# @rbs return: void
|
13
|
+
def self.call: (Ruboty::Message message) -> void
|
14
|
+
|
15
|
+
# @rbs return: void
|
16
|
+
def call: () -> void
|
17
|
+
|
18
|
+
def initialize: (untyped message) -> untyped
|
19
|
+
|
20
|
+
# @rbs %a{memorized}
|
21
|
+
%a{memorized}
|
22
|
+
def database: () -> Ruboty::AiAgent::Database
|
23
|
+
|
24
|
+
# @rbs %a{memorized}
|
25
|
+
%a{memorized}
|
26
|
+
def user: () -> Ruboty::AiAgent::User
|
27
|
+
|
28
|
+
# @rbs %a{memorized}
|
29
|
+
%a{memorized}
|
30
|
+
def chat_thread: () -> Ruboty::AiAgent::ChatThread
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/actions/chat.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module Actions
|
6
|
+
# Chat action for Ruboty::AiAgent
|
7
|
+
class Chat < Base
|
8
|
+
CONVERSATION_KEY: ::Symbol
|
9
|
+
|
10
|
+
# @rbs override
|
11
|
+
def call: ...
|
12
|
+
|
13
|
+
def body_param: () -> String
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/actions/list_ai_commands.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module Actions
|
6
|
+
# ListAiCommands action for Ruboty::AiAgent
|
7
|
+
class ListAiCommands < Base
|
8
|
+
# @rbs override
|
9
|
+
def call: ...
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/actions/list_ai_memories.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module Actions
|
6
|
+
# ListAiMemories action for Ruboty::AiAgent
|
7
|
+
class ListAiMemories < Base
|
8
|
+
def call: () -> untyped
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Generated from lib/ruboty/ai_agent/actions/remove_ai_command.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
module Actions
|
6
|
+
# RemoveAiCommand action for Ruboty::AiAgent
|
7
|
+
class RemoveAiCommand < Base
|
8
|
+
def call: () -> untyped
|
9
|
+
|
10
|
+
def name_param: () -> String
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|