ruboty-ai_agent 0.1.0 → 0.3.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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +19 -0
  3. data/AGENTS.md +8 -0
  4. data/CHANGELOG.md +14 -2
  5. data/Gemfile +10 -3
  6. data/README.md +2 -0
  7. data/Rakefile +4 -0
  8. data/Steepfile +1 -0
  9. data/lib/ruboty/ai_agent/actions/add_ai_command.rb +16 -1
  10. data/lib/ruboty/ai_agent/actions/add_mcp.rb +7 -2
  11. data/lib/ruboty/ai_agent/actions/base.rb +11 -1
  12. data/lib/ruboty/ai_agent/actions/chat.rb +56 -19
  13. data/lib/ruboty/ai_agent/actions/list_ai_commands.rb +20 -4
  14. data/lib/ruboty/ai_agent/actions/list_mcp.rb +50 -3
  15. data/lib/ruboty/ai_agent/actions/remove_ai_command.rb +7 -1
  16. data/lib/ruboty/ai_agent/actions/show_system_prompt.rb +1 -1
  17. data/lib/ruboty/ai_agent/agent.rb +3 -3
  18. data/lib/ruboty/ai_agent/chat_message.rb +7 -2
  19. data/lib/ruboty/ai_agent/chat_thread_messages.rb +40 -0
  20. data/lib/ruboty/ai_agent/commands/base.rb +16 -14
  21. data/lib/ruboty/ai_agent/commands/builtin_base.rb +39 -0
  22. data/lib/ruboty/ai_agent/commands/clear.rb +2 -14
  23. data/lib/ruboty/ai_agent/commands/compact.rb +4 -47
  24. data/lib/ruboty/ai_agent/commands/prompt_command.rb +60 -0
  25. data/lib/ruboty/ai_agent/commands/usage.rb +2 -14
  26. data/lib/ruboty/ai_agent/commands.rb +9 -17
  27. data/lib/ruboty/ai_agent/database/query_methods.rb +31 -6
  28. data/lib/ruboty/ai_agent/database.rb +2 -1
  29. data/lib/ruboty/ai_agent/http_mcp_client.rb +5 -2
  30. data/lib/ruboty/ai_agent/llm/openai.rb +6 -6
  31. data/lib/ruboty/ai_agent/mcp_clients.rb +5 -12
  32. data/lib/ruboty/ai_agent/mcp_configuration.rb +3 -2
  33. data/lib/ruboty/ai_agent/prompt_command_definition.rb +17 -0
  34. data/lib/ruboty/ai_agent/record_set.rb +9 -5
  35. data/lib/ruboty/ai_agent/recordable.rb +11 -9
  36. data/lib/ruboty/ai_agent/request.rb +17 -0
  37. data/lib/ruboty/ai_agent/token_usage.rb +10 -0
  38. data/lib/ruboty/ai_agent/tool.rb +11 -3
  39. data/lib/ruboty/ai_agent/tool_definitions/base.rb +84 -0
  40. data/lib/ruboty/ai_agent/tool_definitions/think.rb +41 -0
  41. data/lib/ruboty/ai_agent/tool_definitions.rb +19 -0
  42. data/lib/ruboty/ai_agent/user.rb +5 -0
  43. data/lib/ruboty/ai_agent/user_mcp_caches.rb +2 -2
  44. data/lib/ruboty/ai_agent/user_mcp_client.rb +5 -4
  45. data/lib/ruboty/ai_agent/user_prompt_command_definitions.rb +17 -0
  46. data/lib/ruboty/ai_agent/version.rb +1 -1
  47. data/lib/ruboty/ai_agent.rb +13 -0
  48. data/lib/ruboty/handlers/ai_agent.rb +28 -16
  49. data/rbs_collection.yaml +1 -0
  50. data/ruboty-ai_agent.gemspec +2 -6
  51. data/script/clean-orphaned-rbs.rb +105 -0
  52. data/script/generate-concern-rbs.rb +5 -5
  53. data/script/generate-data-rbs.rb +3 -5
  54. data/script/generate-memorized-ivar-rbs.rb +6 -11
  55. data/sig/generated/ruboty/ai_agent/actions/add_ai_command.rbs +4 -0
  56. data/sig/generated/ruboty/ai_agent/actions/base.rbs +4 -0
  57. data/sig/generated/ruboty/ai_agent/actions/chat.rbs +10 -0
  58. data/sig/generated/ruboty/ai_agent/actions/list_mcp.rbs +11 -0
  59. data/sig/generated/ruboty/ai_agent/agent.rbs +1 -1
  60. data/sig/generated/ruboty/ai_agent/chat_message.rbs +5 -2
  61. data/sig/generated/ruboty/ai_agent/chat_thread_messages.rbs +14 -0
  62. data/sig/generated/ruboty/ai_agent/commands/base.rbs +8 -19
  63. data/sig/generated/ruboty/ai_agent/commands/builtin_base.rbs +40 -0
  64. data/sig/generated/ruboty/ai_agent/commands/clear.rbs +2 -10
  65. data/sig/generated/ruboty/ai_agent/commands/compact.rbs +2 -20
  66. data/sig/generated/ruboty/ai_agent/commands/prompt_command.rbs +26 -0
  67. data/sig/generated/ruboty/ai_agent/commands/usage.rbs +2 -10
  68. data/sig/generated/ruboty/ai_agent/commands.rbs +3 -4
  69. data/sig/generated/ruboty/ai_agent/database/query_methods.rbs +18 -12
  70. data/sig/generated/ruboty/ai_agent/database.rbs +3 -1
  71. data/sig/generated/ruboty/ai_agent/llm/openai.rbs +3 -3
  72. data/sig/generated/ruboty/ai_agent/mcp_clients.rbs +0 -5
  73. data/sig/generated/ruboty/ai_agent/mcp_configuration.rbs +4 -2
  74. data/sig/generated/ruboty/ai_agent/prompt_command_definition.rbs +23 -0
  75. data/sig/generated/ruboty/ai_agent/record_set.rbs +11 -9
  76. data/sig/generated/ruboty/ai_agent/recordable.rbs +6 -6
  77. data/sig/generated/ruboty/ai_agent/request.rbs +23 -0
  78. data/sig/generated/ruboty/ai_agent/token_usage.rbs +4 -0
  79. data/sig/generated/ruboty/ai_agent/tool.rbs +9 -3
  80. data/sig/generated/ruboty/ai_agent/tool_definitions/base.rbs +52 -0
  81. data/sig/generated/ruboty/ai_agent/tool_definitions/think.rbs +17 -0
  82. data/sig/generated/ruboty/ai_agent/tool_definitions.rbs +12 -0
  83. data/sig/generated/ruboty/ai_agent/user.rbs +4 -0
  84. data/sig/generated/ruboty/ai_agent/user_mcp_client.rbs +4 -2
  85. data/sig/generated/ruboty/ai_agent/user_prompt_command_definitions.rbs +12 -0
  86. data/sig/generated/ruboty/handlers/ai_agent.rbs +12 -12
  87. data/sig/generated-by-scripts/concerns.rbs +5 -0
  88. data/sig/generated-by-scripts/memorized_ivars.rbs +19 -0
  89. metadata +19 -57
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruboty
4
+ module AiAgent
5
+ module ToolDefinitions
6
+ # Base class for tool definitions
7
+ # @abstract
8
+ class Base
9
+ # @rbs!
10
+ # type input_schema = Hash[String | Symbol, untyped]
11
+
12
+ # @rbs!
13
+ # def self.tool_name: () -> String?
14
+ # def self.tool_name=: (String) -> String
15
+ # def self.tool_title: () -> String?
16
+ # def self.tool_title=: (String) -> String
17
+ # def self.tool_description: () -> String?
18
+ # def self.tool_description=: (String) -> String
19
+ # def self.tool_input_schema: () -> input_schema?
20
+ # def self.tool_input_schema=: (input_schema) -> input_schema
21
+
22
+ class << self
23
+ # @rbs skip
24
+ attr_accessor :tool_name
25
+
26
+ # @rbs skip
27
+ attr_accessor :tool_title
28
+
29
+ # @rbs skip
30
+ attr_accessor :tool_description
31
+
32
+ # @rbs skip
33
+ attr_accessor :tool_input_schema
34
+ end
35
+
36
+ def tool_name #: String
37
+ self.class.tool_name || (raise NotImplementedError, "Subclasses must define 'tool_name'")
38
+ end
39
+
40
+ def tool_title #: String
41
+ self.class.tool_title || ''
42
+ end
43
+
44
+ def tool_description #: String
45
+ self.class.tool_description || ''
46
+ end
47
+
48
+ def tool_input_schema #: input_schema
49
+ self.class.tool_input_schema || {} #: input_schema
50
+ end
51
+
52
+ # Return true if you want the tool not to produce call logs in the chat.
53
+ def silent? #: boolish
54
+ false
55
+ end
56
+
57
+ attr_reader :request #: Request
58
+
59
+ # @rbs request: Request
60
+ def initialize(request:)
61
+ @request = request
62
+ end
63
+
64
+ # @abstract
65
+ # @rbs arguments: Hash[String, untyped]
66
+ # @rbs return: String?
67
+ def call(arguments)
68
+ raise NotImplementedError, "Subclasses must implement the 'call' method"
69
+ end
70
+
71
+ def to_tool #: Tool
72
+ Tool.new(
73
+ name: tool_name,
74
+ title: tool_title,
75
+ description: tool_description,
76
+ input_schema: tool_input_schema,
77
+ silent: silent?,
78
+ &method(:call) #: ^ (Hash[String, untyped]) -> String?
79
+ )
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruboty
4
+ module AiAgent
5
+ module ToolDefinitions
6
+ # Tool
7
+ class Think < Base
8
+ self.tool_name = 'think'
9
+ self.tool_title = 'Think'
10
+
11
+ self.tool_description = <<~TEXT
12
+ Use this tool to think abount something. It will not obtain new information or make any changes, but just log the thought.
13
+ Use it when completx reasoing or brainstorming is needed.
14
+ TEXT
15
+
16
+ self.tool_input_schema = {
17
+ type: 'object',
18
+ properties: {
19
+ thought: { type: 'string', description: 'Your thought.' }
20
+ },
21
+ required: ['thought']
22
+ }
23
+
24
+ # @rbs arguments: Hash[String, untyped]
25
+ # @rbs return: String?
26
+ def call(arguments)
27
+ thought = arguments['thought']
28
+
29
+ request.message.reply("Thought:\n#{thought}")
30
+
31
+ thought
32
+ end
33
+
34
+ # @rbs override
35
+ def silent?
36
+ true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruboty
4
+ module AiAgent
5
+ # Tool Definitions for AI Agent
6
+ module ToolDefinitions
7
+ autoload :Base, 'ruboty/ai_agent/tool_definitions/base'
8
+ autoload :Think, 'ruboty/ai_agent/tool_definitions/think'
9
+
10
+ # @rbs request: Request
11
+ # @rbs return: Array[Base]
12
+ def self.builtins(request:)
13
+ [
14
+ Think
15
+ ].map { |tool_def| tool_def.new(request:) }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -47,6 +47,11 @@ module Ruboty
47
47
  def mcp_tools_caches #: UserMcpToolsCaches
48
48
  @mcp_tools_caches ||= UserMcpToolsCaches.new(database: database, user_id: id)
49
49
  end
50
+
51
+ # @rbs %a{memorized}
52
+ def prompt_command_definitions #: UserPromptCommandDefinitions
53
+ @prompt_command_definitions ||= UserPromptCommandDefinitions.new(database: database, user_id: id)
54
+ end
50
55
  end
51
56
  end
52
57
  end
@@ -57,12 +57,12 @@ module Ruboty
57
57
 
58
58
  # @rbs override
59
59
  def all
60
- super().reject { |(_key, cache)| cache.expired? }
60
+ super.reject { |(_key, cache)| cache.expired? }
61
61
  end
62
62
 
63
63
  # @rbs override
64
64
  def fetch(key)
65
- cache = super(key)
65
+ cache = super
66
66
 
67
67
  if cache&.expired?
68
68
  remove(key)
@@ -26,8 +26,8 @@ module Ruboty
26
26
  # @rbs arguments: Hash[String, untyped]
27
27
  # @rbs &block: ? (Hash[String, untyped]) -> void
28
28
  # @rbs return: untyped
29
- def call_tool(name, arguments = {}, &block)
30
- mcp_client.call_tool(name, arguments, &block)
29
+ def call_tool(name, arguments = {}, &)
30
+ mcp_client.call_tool(name, arguments, &)
31
31
  end
32
32
 
33
33
  # @rbs return: untyped
@@ -68,14 +68,15 @@ module Ruboty
68
68
  mcp_client.cleanup_session
69
69
  end
70
70
 
71
- private
72
-
73
71
  # @rbs return: McpConfiguration
74
72
  def configuration
75
73
  user.mcp_configurations.all_values.find { |config| config.name == mcp_name } ||
76
74
  raise("MCP configuration not found: #{mcp_name}")
77
75
  end
78
76
 
77
+ private
78
+
79
+ # @rbs %a{memorized}
79
80
  # @rbs return: HttpMcpClient
80
81
  def mcp_client
81
82
  @mcp_client ||= case configuration.transport
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruboty
4
+ module AiAgent
5
+ # User-defined commands record set
6
+ class UserPromptCommandDefinitions < UserAssociations #[PromptCommandDefinition]
7
+ self.association_key = :user_prompt_command_definitions
8
+
9
+ # @rbs command: PromptCommandDefinition
10
+ # @rbs return: PromptCommandDefinition
11
+ def add(command)
12
+ store(command, key: command.name)
13
+ command
14
+ end
15
+ end
16
+ end
17
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ruboty
4
4
  module AiAgent
5
- VERSION = '0.1.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -25,10 +25,13 @@ module Ruboty
25
25
  autoload :McpClient, 'ruboty/ai_agent/mcp_client'
26
26
  autoload :McpClients, 'ruboty/ai_agent/mcp_clients'
27
27
  autoload :McpConfiguration, 'ruboty/ai_agent/mcp_configuration'
28
+ autoload :PromptCommandDefinition, 'ruboty/ai_agent/prompt_command_definition'
28
29
  autoload :Recordable, 'ruboty/ai_agent/recordable'
29
30
  autoload :RecordSet, 'ruboty/ai_agent/record_set'
31
+ autoload :Request, 'ruboty/ai_agent/request'
30
32
  autoload :TokenUsage, 'ruboty/ai_agent/token_usage'
31
33
  autoload :Tool, 'ruboty/ai_agent/tool'
34
+ autoload :ToolDefinitions, 'ruboty/ai_agent/tool_definitions'
32
35
  autoload :User, 'ruboty/ai_agent/user'
33
36
  autoload :UserAiMemories, 'ruboty/ai_agent/user_ai_memories'
34
37
  autoload :UserAssociations, 'ruboty/ai_agent/user_associations'
@@ -36,5 +39,15 @@ module Ruboty
36
39
  autoload :UserMcpClient, 'ruboty/ai_agent/user_mcp_client'
37
40
  autoload :UserMcpConfigurations, 'ruboty/ai_agent/user_mcp_configurations'
38
41
  autoload :UserMcpToolsCaches, 'ruboty/ai_agent/user_mcp_tools_caches'
42
+ autoload :UserPromptCommandDefinitions, 'ruboty/ai_agent/user_prompt_command_definitions'
43
+
44
+ # Ensure all recordables are loaded
45
+ [
46
+ CachedValue,
47
+ ChatMessage,
48
+ McpConfiguration,
49
+ PromptCommandDefinition,
50
+ TokenUsage
51
+ ]
39
52
  end
40
53
  end
@@ -18,66 +18,78 @@ module Ruboty
18
18
 
19
19
  on(/add mcp (?<name>\S+)\s+(?<config>.+)\z/, name: 'add_mcp', description: 'Add a new MCP server')
20
20
  on(/remove mcp (?<name>\S+)/, name: 'remove_mcp', description: 'Remove the specified MCP server')
21
- on(/list mcps?/, name: 'list_mcp', description: 'List configured MCP servers')
21
+ on(/list mcps?(?<with_headers>\s+with\s+headers)?/, name: 'list_mcp', description: 'List configured MCP servers')
22
22
 
23
- on(/set system prompt "(?<prompt>.+?)"(?: in (?<scope>user|global) scope)?/, name: 'set_system_prompt', description: 'Set system prompt')
23
+ on(/set (?:(?<scope>user|global) )?system prompt "(?<prompt>.+?)"/, name: 'set_system_prompt', description: 'Set system prompt')
24
24
  on(/show system prompt/, name: 'show_system_prompt', description: 'Show system prompt')
25
25
 
26
26
  on(/add ai memory "(?<prompt>.+)"/, name: 'add_ai_memory', description: 'Add a new AI memory')
27
27
  on(/remove ai memory (?<index>\d+)/, name: 'remove_ai_memory', description: 'Remove the specified AI memory')
28
28
  on(/list ai memor(?:y|ies)/, name: 'list_mcp', description: "List AI's memories")
29
29
 
30
- on(%r{add ai command /(?<name>\S+)\s+"(?<prompt>.+)"}, name: 'add_ai_command',
31
- description: 'Add a new AI command')
30
+ on(%r{add ai command /(?<name>\S+)\s+(?<prompt>.+)}, name: 'add_ai_command',
31
+ description: 'Add a new AI command')
32
32
  on(%r{remove ai command /(?<name>\S+)}, name: 'remove_ai_command', description: 'Remove the specified AI command')
33
33
  on(/list ai commands?/, name: 'list_ai_commands', description: 'List AI commands')
34
34
 
35
- def chat(message)
35
+ def chat(message) #: true
36
36
  Ruboty::AiAgent::Actions::Chat.call(message)
37
+ true
37
38
  end
38
39
 
39
- def add_mcp(message)
40
+ def add_mcp(message) #: true
40
41
  Ruboty::AiAgent::Actions::AddMcp.call(message)
42
+ true
41
43
  end
42
44
 
43
- def remove_mcp(message)
45
+ def remove_mcp(message) #: true
44
46
  Ruboty::AiAgent::Actions::RemoveMcp.call(message)
47
+ true
45
48
  end
46
49
 
47
- def list_mcp(message)
50
+ def list_mcp(message) #: true
48
51
  Ruboty::AiAgent::Actions::ListMcp.call(message)
52
+ true
49
53
  end
50
54
 
51
- def set_system_prompt(message)
55
+ def set_system_prompt(message) #: true
52
56
  Ruboty::AiAgent::Actions::SetSystemPrompt.call(message)
57
+ true
53
58
  end
54
59
 
55
- def show_system_prompt(message)
60
+ def show_system_prompt(message) #: true
56
61
  Ruboty::AiAgent::Actions::ShowSystemPrompt.call(message)
62
+ true
57
63
  end
58
64
 
59
- def add_ai_memory(message)
65
+ def add_ai_memory(message) #: true
60
66
  Ruboty::AiAgent::Actions::AddAiMemory.call(message)
67
+ true
61
68
  end
62
69
 
63
- def remove_ai_memory(message)
70
+ def remove_ai_memory(message) #: true
64
71
  Ruboty::AiAgent::Actions::RemoveAiMemory.call(message)
72
+ true
65
73
  end
66
74
 
67
- def list_ai_memories(message)
75
+ def list_ai_memories(message) #: true
68
76
  Ruboty::AiAgent::Actions::ListAiMemories.call(message)
77
+ true
69
78
  end
70
79
 
71
- def add_ai_command(message)
80
+ def add_ai_command(message) #: true
72
81
  Ruboty::AiAgent::Actions::AddAiCommand.call(message)
82
+ true
73
83
  end
74
84
 
75
- def remove_ai_command(message)
85
+ def remove_ai_command(message) #: true
76
86
  Ruboty::AiAgent::Actions::RemoveAiCommand.call(message)
87
+ true
77
88
  end
78
89
 
79
- def list_ai_commands(message)
90
+ def list_ai_commands(message) #: true
80
91
  Ruboty::AiAgent::Actions::ListAiCommands.call(message)
92
+ true
81
93
  end
82
94
  end
83
95
  end
data/rbs_collection.yaml CHANGED
@@ -18,6 +18,7 @@ gems:
18
18
  - name: net-http
19
19
  - name: json
20
20
  - name: optparse
21
+ - name: shellwords
21
22
  # # If you want to avoid installing rbs files for gems, you can specify them here.
22
23
  # - name: GEM_NAME
23
24
  # ignore: true
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.metadata['changelog_uri'] = 'https://github.com/tomoasleep/ruboty-ai_agent/blob/main/CHANGELOG.md'
27
27
  else
28
28
  raise 'RubyGems 2.0 or newer is required to protect against ' \
29
- 'public gem pushes.'
29
+ 'public gem pushes.'
30
30
  end
31
31
 
32
32
  # Specify which files should be added to the gem when it is released.
@@ -38,12 +38,8 @@ Gem::Specification.new do |spec|
38
38
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
39
39
  spec.require_paths = ['lib']
40
40
 
41
- spec.add_development_dependency 'bundler', '~> 2'
42
- spec.add_development_dependency 'rake', '~> 13'
43
- spec.add_development_dependency 'rspec', '~> 3.0'
44
- spec.add_development_dependency 'webmock', '~> 3.0'
45
-
46
41
  spec.add_dependency 'event_stream_parser', '~> 1.0'
47
42
  spec.add_dependency 'openai', '~> 0.22.0'
48
43
  spec.add_dependency 'ruboty', '~> 1.3'
44
+ spec.metadata['rubygems_mfa_required'] = 'true'
49
45
  end
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Clean orphaned RBS files from sig/generated
5
+ #
6
+ # This script removes RBS files from sig/generated directory that don't have
7
+ # corresponding Ruby files in the project.
8
+
9
+ require 'fileutils'
10
+ require 'pathname'
11
+
12
+ # Remove orphaned RBS files by `rbs-inline`
13
+ class OrphanedRbsCleaner
14
+ def initialize(project_root: Dir.pwd)
15
+ @project_root = Pathname.new(project_root)
16
+ @sig_generated_dir = @project_root / 'sig' / 'generated'
17
+ @removed_files = []
18
+ @kept_files = []
19
+ end
20
+
21
+ def run(dry_run: false)
22
+ return unless @sig_generated_dir.exist?
23
+
24
+ puts "Scanning for orphaned RBS files in #{@sig_generated_dir}..."
25
+
26
+ rbs_files = find_rbs_files
27
+ puts "Found #{rbs_files.size} RBS files to check"
28
+
29
+ rbs_files.each do |rbs_file|
30
+ corresponding_ruby_file = find_corresponding_ruby_file(rbs_file)
31
+
32
+ if corresponding_ruby_file&.exist?
33
+ @kept_files << rbs_file
34
+ else
35
+ @removed_files << rbs_file
36
+ if dry_run
37
+ puts "Would remove: #{rbs_file.relative_path_from(@project_root)}"
38
+ else
39
+ puts "Removing: #{rbs_file.relative_path_from(@project_root)}"
40
+ rbs_file.delete
41
+ end
42
+ end
43
+ end
44
+
45
+ cleanup_empty_directories unless dry_run
46
+
47
+ print_summary
48
+ end
49
+
50
+ private
51
+
52
+ def find_rbs_files
53
+ @sig_generated_dir.glob('**/*.rbs').sort
54
+ end
55
+
56
+ def find_corresponding_ruby_file(rbs_file)
57
+ # Convert sig/generated/path/to/file.rbs to possible Ruby file locations
58
+ relative_path = rbs_file.relative_path_from(@sig_generated_dir)
59
+ ruby_file_name = relative_path.sub_ext('.rb')
60
+
61
+ # Check common Ruby source directories
62
+ possible_locations = [
63
+ @project_root / 'lib' / ruby_file_name
64
+ ]
65
+
66
+ possible_locations.find(&:exist?)
67
+ end
68
+
69
+ def cleanup_empty_directories
70
+ # Remove empty directories in sig/generated
71
+ @sig_generated_dir.find do |path|
72
+ next unless path.directory?
73
+ next if path == @sig_generated_dir
74
+
75
+ begin
76
+ path.rmdir if path.empty?
77
+ rescue Errno::ENOTEMPTY
78
+ # Directory is not empty, skip
79
+ end
80
+ end
81
+ end
82
+
83
+ def print_summary
84
+ puts "\nSummary:"
85
+ puts " Kept files: #{@kept_files.size}"
86
+ puts " Removed files: #{@removed_files.size}"
87
+
88
+ return unless @removed_files.any?
89
+
90
+ puts "\nRemoved files:"
91
+ @removed_files.each do |file|
92
+ puts " - #{file.relative_path_from(@project_root)}"
93
+ end
94
+ end
95
+ end
96
+
97
+ # Script execution
98
+ if __FILE__ == $PROGRAM_NAME
99
+ dry_run = ARGV.include?('--dry-run')
100
+
101
+ puts 'Running in dry-run mode (no files will be deleted)' if dry_run
102
+
103
+ cleaner = OrphanedRbsCleaner.new
104
+ cleaner.run(dry_run: dry_run)
105
+ end
@@ -303,7 +303,7 @@ class ConcernRbsGenerator
303
303
 
304
304
  # Open namespaces
305
305
  namespace_parts.each do |part|
306
- content << (' ' * indent_level) + "module #{part}"
306
+ content << ((' ' * indent_level) + "module #{part}")
307
307
  indent_level += 1
308
308
  end
309
309
 
@@ -315,17 +315,17 @@ class ConcernRbsGenerator
315
315
  # Add type parameters if present
316
316
  if includer[:type_params] && !includer[:type_params].empty?
317
317
  type_params_str = "[#{includer[:type_params].join(', ')}]"
318
- content << (' ' * indent_level) + "#{keyword} #{simple_name}#{type_params_str}"
318
+ content << ((' ' * indent_level) + "#{keyword} #{simple_name}#{type_params_str}")
319
319
  else
320
- content << (' ' * indent_level) + "#{keyword} #{simple_name}"
320
+ content << ((' ' * indent_level) + "#{keyword} #{simple_name}")
321
321
  end
322
322
 
323
323
  concern = includer[:concern]
324
324
  concern_simple_name = concern[:name].sub(/^::/, '').split('::').last
325
325
 
326
- content << (' ' * (indent_level + 1)) + "extend #{concern_simple_name}::ClassMethods" if concern[:has_class_methods]
326
+ content << ((' ' * (indent_level + 1)) + "extend #{concern_simple_name}::ClassMethods") if concern[:has_class_methods]
327
327
 
328
- content << (' ' * (indent_level + 1)) + "prepend #{concern_simple_name}::PrependMethods" if concern[:has_prepend_methods]
328
+ content << ((' ' * (indent_level + 1)) + "prepend #{concern_simple_name}::PrependMethods") if concern[:has_prepend_methods]
329
329
 
330
330
  content << "#{' ' * indent_level}end"
331
331
  content << ''
@@ -31,7 +31,7 @@ class Processor
31
31
  puts "Scanning Ruby files in #{@lib_path}..."
32
32
 
33
33
  @lib_path.glob('**/*.rb').map do |file|
34
- puts "Processing #{file}..."
34
+ puts "Processing #{file}..." if ENV['DEBUG']
35
35
  content = file.read
36
36
 
37
37
  parsed = Prism.parse(content)
@@ -127,9 +127,7 @@ class DataClassRbsGenerator
127
127
 
128
128
  def extract_constant_name(constant_path)
129
129
  case constant_path
130
- when Prism::ConstantReadNode
131
- constant_path.name.to_s
132
- when Prism::ConstantPathNode
130
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
133
131
  constant_path.name.to_s
134
132
  end
135
133
  end
@@ -242,7 +240,7 @@ class DataClassRbsGenerator
242
240
  end
243
241
 
244
242
  def indent(string, level:)
245
- ' ' * level + string
243
+ (' ' * level) + string
246
244
  end
247
245
  end
248
246
  end
@@ -101,15 +101,15 @@ class MemorizedIvarRbsGenerator
101
101
 
102
102
  # Open namespaces
103
103
  parts[0...-1].each do |part|
104
- content << (' ' * indent_level) + "module #{part}"
104
+ content << ((' ' * indent_level) + "module #{part}")
105
105
  indent_level += 1
106
106
  end
107
107
 
108
108
  # Class declaration with instance variables
109
- content << (' ' * indent_level) + "class #{parts.last}"
109
+ content << ((' ' * indent_level) + "class #{parts.last}")
110
110
 
111
111
  methods.each do |method|
112
- content << (' ' * (indent_level + 1)) + "@#{method.ivar_name}: #{method.return_type}"
112
+ content << ((' ' * (indent_level + 1)) + "@#{method.ivar_name}: #{method.return_type}")
113
113
  end
114
114
 
115
115
  content << "#{' ' * indent_level}end"
@@ -188,9 +188,7 @@ class MemorizedIvarRbsGenerator
188
188
 
189
189
  def extract_constant_name(constant_path)
190
190
  case constant_path
191
- when Prism::ConstantReadNode
192
- constant_path.name.to_s
193
- when Prism::ConstantPathNode
191
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
194
192
  constant_path.name.to_s
195
193
  end
196
194
  end
@@ -248,11 +246,8 @@ class MemorizedIvarRbsGenerator
248
246
  return result if result
249
247
  end
250
248
  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 = ...
249
+ when Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableWriteNode
250
+ # This handles @ivar ||= ... or @ivar = ...
256
251
  node.name.to_s.delete('@')
257
252
  else
258
253
  # Recursively search in child nodes
@@ -10,6 +10,10 @@ module Ruboty
10
10
  def name_param: () -> String
11
11
 
12
12
  def prompt_param: () -> String
13
+
14
+ # @rbs str: String
15
+ # @rbs return: String
16
+ def undump_string: (String str) -> String
13
17
  end
14
18
  end
15
19
  end
@@ -28,6 +28,10 @@ module Ruboty
28
28
  # @rbs %a{memorized}
29
29
  %a{memorized}
30
30
  def chat_thread: () -> Ruboty::AiAgent::ChatThread
31
+
32
+ private
33
+
34
+ def thread_id: () -> String
31
35
  end
32
36
  end
33
37
  end
@@ -11,6 +11,16 @@ module Ruboty
11
11
  def call: ...
12
12
 
13
13
  def body_param: () -> String
14
+
15
+ # @rbs %a{memorized}
16
+ %a{memorized}
17
+ def request: () -> Request
18
+
19
+ private
20
+
21
+ # @rbs body: String
22
+ # @rbs return: void
23
+ def complete_chat: (String body) -> void
14
24
  end
15
25
  end
16
26
  end
@@ -6,6 +6,17 @@ module Ruboty
6
6
  # ListMcp action for Ruboty::AiAgent
7
7
  class ListMcp < Base
8
8
  def call: () -> untyped
9
+
10
+ private
11
+
12
+ # @rbs client: UserMcpClient
13
+ # @rbs show_headers: bool
14
+ # @rbs return: String
15
+ def format_mcp_client: (UserMcpClient client, ?show_headers: bool) -> String
16
+
17
+ # @rbs client: UserMcpClient
18
+ # @rbs return: String
19
+ def format_tools: (UserMcpClient client) -> String
9
20
  end
10
21
  end
11
22
  end
@@ -21,7 +21,7 @@ module Ruboty
21
21
 
22
22
  def on_tool_call: (tool: untyped, tool_arguments: untyped) ?{ (?) -> untyped } -> untyped
23
23
 
24
- def on_tool_response: (tool_response: untyped, message: untyped) ?{ (?) -> untyped } -> untyped
24
+ def on_tool_response: (tool: untyped, tool_response: untyped, message: untyped) ?{ (?) -> untyped } -> untyped
25
25
 
26
26
  def on_response: (untyped response) ?{ (?) -> untyped } -> untyped
27
27
  end