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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +45 -0
  5. data/AGENTS.md +22 -0
  6. data/CHANGELOG.md +3 -0
  7. data/CLAUDE.md +1 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +14 -0
  10. data/README.md +118 -0
  11. data/Rakefile +47 -0
  12. data/Steepfile +12 -0
  13. data/bin/console +15 -0
  14. data/bin/ruboty +34 -0
  15. data/bin/setup +8 -0
  16. data/lib/ruboty/ai_agent/actions/add_ai_command.rb +22 -0
  17. data/lib/ruboty/ai_agent/actions/add_ai_memory.rb +20 -0
  18. data/lib/ruboty/ai_agent/actions/add_mcp.rb +94 -0
  19. data/lib/ruboty/ai_agent/actions/base.rb +43 -0
  20. data/lib/ruboty/ai_agent/actions/chat.rb +64 -0
  21. data/lib/ruboty/ai_agent/actions/list_ai_commands.rb +19 -0
  22. data/lib/ruboty/ai_agent/actions/list_ai_memories.rb +18 -0
  23. data/lib/ruboty/ai_agent/actions/list_mcp.rb +18 -0
  24. data/lib/ruboty/ai_agent/actions/remove_ai_command.rb +18 -0
  25. data/lib/ruboty/ai_agent/actions/remove_ai_memory.rb +25 -0
  26. data/lib/ruboty/ai_agent/actions/remove_mcp.rb +24 -0
  27. data/lib/ruboty/ai_agent/actions/set_system_prompt.rb +31 -0
  28. data/lib/ruboty/ai_agent/actions/show_system_prompt.rb +30 -0
  29. data/lib/ruboty/ai_agent/actions.rb +22 -0
  30. data/lib/ruboty/ai_agent/agent.rb +71 -0
  31. data/lib/ruboty/ai_agent/cached_value.rb +43 -0
  32. data/lib/ruboty/ai_agent/chat_message.rb +60 -0
  33. data/lib/ruboty/ai_agent/chat_thread.rb +31 -0
  34. data/lib/ruboty/ai_agent/chat_thread_associations.rb +34 -0
  35. data/lib/ruboty/ai_agent/chat_thread_messages.rb +17 -0
  36. data/lib/ruboty/ai_agent/commands/base.rb +39 -0
  37. data/lib/ruboty/ai_agent/commands/clear.rb +29 -0
  38. data/lib/ruboty/ai_agent/commands/compact.rb +80 -0
  39. data/lib/ruboty/ai_agent/commands/usage.rb +52 -0
  40. data/lib/ruboty/ai_agent/commands.rb +33 -0
  41. data/lib/ruboty/ai_agent/database/query_methods.rb +84 -0
  42. data/lib/ruboty/ai_agent/database.rb +40 -0
  43. data/lib/ruboty/ai_agent/global_settings.rb +33 -0
  44. data/lib/ruboty/ai_agent/http_mcp_client.rb +215 -0
  45. data/lib/ruboty/ai_agent/llm/openai/model.rb +29 -0
  46. data/lib/ruboty/ai_agent/llm/openai.rb +181 -0
  47. data/lib/ruboty/ai_agent/llm/response.rb +21 -0
  48. data/lib/ruboty/ai_agent/llm.rb +11 -0
  49. data/lib/ruboty/ai_agent/mcp_clients.rb +48 -0
  50. data/lib/ruboty/ai_agent/mcp_configuration.rb +31 -0
  51. data/lib/ruboty/ai_agent/record_set.rb +71 -0
  52. data/lib/ruboty/ai_agent/recordable.rb +116 -0
  53. data/lib/ruboty/ai_agent/token_usage.rb +45 -0
  54. data/lib/ruboty/ai_agent/tool.rb +29 -0
  55. data/lib/ruboty/ai_agent/user.rb +52 -0
  56. data/lib/ruboty/ai_agent/user_ai_memories.rb +17 -0
  57. data/lib/ruboty/ai_agent/user_associations.rb +34 -0
  58. data/lib/ruboty/ai_agent/user_mcp_caches.rb +90 -0
  59. data/lib/ruboty/ai_agent/user_mcp_client.rb +93 -0
  60. data/lib/ruboty/ai_agent/user_mcp_configurations.rb +15 -0
  61. data/lib/ruboty/ai_agent/user_mcp_tools_caches.rb +14 -0
  62. data/lib/ruboty/ai_agent/version.rb +7 -0
  63. data/lib/ruboty/ai_agent.rb +40 -0
  64. data/lib/ruboty/handlers/ai_agent.rb +84 -0
  65. data/rbs_collection.yaml +23 -0
  66. data/ruboty-ai_agent.gemspec +49 -0
  67. data/script/generate-concern-rbs.rb +351 -0
  68. data/script/generate-data-rbs.rb +250 -0
  69. data/script/generate-memorized-ivar-rbs.rb +292 -0
  70. data/sig/generated/ruboty/ai_agent/actions/add_ai_command.rbs +16 -0
  71. data/sig/generated/ruboty/ai_agent/actions/add_ai_memory.rbs +14 -0
  72. data/sig/generated/ruboty/ai_agent/actions/add_mcp.rbs +26 -0
  73. data/sig/generated/ruboty/ai_agent/actions/base.rbs +34 -0
  74. data/sig/generated/ruboty/ai_agent/actions/chat.rbs +17 -0
  75. data/sig/generated/ruboty/ai_agent/actions/list_ai_commands.rbs +13 -0
  76. data/sig/generated/ruboty/ai_agent/actions/list_ai_memories.rbs +12 -0
  77. data/sig/generated/ruboty/ai_agent/actions/list_mcp.rbs +12 -0
  78. data/sig/generated/ruboty/ai_agent/actions/remove_ai_command.rbs +14 -0
  79. data/sig/generated/ruboty/ai_agent/actions/remove_ai_memory.rbs +14 -0
  80. data/sig/generated/ruboty/ai_agent/actions/remove_mcp.rbs +14 -0
  81. data/sig/generated/ruboty/ai_agent/actions/set_system_prompt.rbs +16 -0
  82. data/sig/generated/ruboty/ai_agent/actions/show_system_prompt.rbs +12 -0
  83. data/sig/generated/ruboty/ai_agent/actions.rbs +9 -0
  84. data/sig/generated/ruboty/ai_agent/agent.rbs +29 -0
  85. data/sig/generated/ruboty/ai_agent/cached_value.rbs +28 -0
  86. data/sig/generated/ruboty/ai_agent/chat_message.rbs +34 -0
  87. data/sig/generated/ruboty/ai_agent/chat_thread.rbs +22 -0
  88. data/sig/generated/ruboty/ai_agent/chat_thread_associations.rbs +21 -0
  89. data/sig/generated/ruboty/ai_agent/chat_thread_messages.rbs +13 -0
  90. data/sig/generated/ruboty/ai_agent/commands/base.rbs +40 -0
  91. data/sig/generated/ruboty/ai_agent/commands/clear.rbs +20 -0
  92. data/sig/generated/ruboty/ai_agent/commands/compact.rbs +30 -0
  93. data/sig/generated/ruboty/ai_agent/commands/usage.rbs +26 -0
  94. data/sig/generated/ruboty/ai_agent/commands.rbs +13 -0
  95. data/sig/generated/ruboty/ai_agent/database/query_methods.rbs +39 -0
  96. data/sig/generated/ruboty/ai_agent/database.rbs +27 -0
  97. data/sig/generated/ruboty/ai_agent/global_settings.rbs +23 -0
  98. data/sig/generated/ruboty/ai_agent/http_mcp_client.rbs +62 -0
  99. data/sig/generated/ruboty/ai_agent/llm/openai/model.rbs +21 -0
  100. data/sig/generated/ruboty/ai_agent/llm/openai.rbs +54 -0
  101. data/sig/generated/ruboty/ai_agent/llm/response.rbs +29 -0
  102. data/sig/generated/ruboty/ai_agent/llm.rbs +9 -0
  103. data/sig/generated/ruboty/ai_agent/mcp_clients.rbs +24 -0
  104. data/sig/generated/ruboty/ai_agent/mcp_configuration.rbs +35 -0
  105. data/sig/generated/ruboty/ai_agent/record_set.rbs +42 -0
  106. data/sig/generated/ruboty/ai_agent/recordable.rbs +56 -0
  107. data/sig/generated/ruboty/ai_agent/token_usage.rbs +30 -0
  108. data/sig/generated/ruboty/ai_agent/tool.rbs +27 -0
  109. data/sig/generated/ruboty/ai_agent/user.rbs +35 -0
  110. data/sig/generated/ruboty/ai_agent/user_ai_memories.rbs +11 -0
  111. data/sig/generated/ruboty/ai_agent/user_associations.rbs +21 -0
  112. data/sig/generated/ruboty/ai_agent/user_mcp_caches.rbs +44 -0
  113. data/sig/generated/ruboty/ai_agent/user_mcp_client.rbs +58 -0
  114. data/sig/generated/ruboty/ai_agent/user_mcp_configurations.rbs +11 -0
  115. data/sig/generated/ruboty/ai_agent/user_mcp_tools_caches.rbs +11 -0
  116. data/sig/generated/ruboty/ai_agent/version.rbs +7 -0
  117. data/sig/generated/ruboty/ai_agent.rbs +9 -0
  118. data/sig/generated/ruboty/handlers/ai_agent.rbs +32 -0
  119. data/sig/generated-by-scripts/concerns.rbs +27 -0
  120. data/sig/generated-by-scripts/memorized_ivars.rbs +42 -0
  121. data/sig-lib/event_stream_parser/event_stream_parser.rbs +21 -0
  122. data/sig-lib/mem/mem.rbs +19 -0
  123. data/sig-lib/ruboty/ruboty.rbs +421 -0
  124. 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,12 @@
1
+ # Generated from lib/ruboty/ai_agent/actions/list_mcp.rb with RBS::Inline
2
+
3
+ module Ruboty
4
+ module AiAgent
5
+ module Actions
6
+ # ListMcp action for Ruboty::AiAgent
7
+ class ListMcp < 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