girb 0.1.2 → 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.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Girb
6
+ module Tools
7
+ class RunDebugCommand < Base
8
+ class << self
9
+ def description
10
+ "Execute a debugger command. Use this tool whenever the user asks to step, continue, set breakpoints, or perform any debugger action. " \
11
+ "For conditional breakpoints, use 'if:' (with colon): e.g., 'break sample.rb:14 if: x == 1'."
12
+ end
13
+
14
+ def parameters
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ command: {
19
+ type: "string",
20
+ description: "The debugger command to execute. Examples: 'n', 's', 'c', 'finish', 'up', 'down', " \
21
+ "'break sample.rb:14', 'break sample.rb:14 if: x == 1', 'info locals', 'bt'"
22
+ },
23
+ auto_continue: {
24
+ type: "boolean",
25
+ description: "Set to true if you want to be re-invoked after the command executes " \
26
+ "to see the new state. Use this when you need to check variables or " \
27
+ "decide the next action after stepping/continuing."
28
+ }
29
+ },
30
+ required: ["command"]
31
+ }
32
+ end
33
+
34
+ def available?
35
+ defined?(DEBUGGER__)
36
+ end
37
+ end
38
+
39
+ def execute(binding, command:, auto_continue: false)
40
+ Girb::DebugIntegration.add_pending_debug_command(command)
41
+ Girb::DebugIntegration.auto_continue = true if auto_continue
42
+ { success: true, command: command, auto_continue: auto_continue,
43
+ message: auto_continue ?
44
+ "Command '#{command}' will be executed. You will be re-invoked with updated context." :
45
+ "Command '#{command}' will be executed after this response." }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Girb
6
+ module Tools
7
+ class RunIrbDebugCommand < Base
8
+ class << self
9
+ def name
10
+ "run_debug_command"
11
+ end
12
+
13
+ def description
14
+ "Execute a debug command in IRB. IRB integrates with debug gem, allowing step-by-step debugging. " \
15
+ "Use this when the user asks to step through code, set breakpoints, or navigate execution."
16
+ end
17
+
18
+ def parameters
19
+ {
20
+ type: "object",
21
+ properties: {
22
+ command: {
23
+ type: "string",
24
+ description: "The debug command to execute. Examples: 'next', 'step', 'continue', 'finish', " \
25
+ "'break sample.rb:14', 'break sample.rb:14 if: x == 1', 'info', 'backtrace'"
26
+ },
27
+ auto_continue: {
28
+ type: "boolean",
29
+ description: "Set to true to be re-invoked after the command executes to see the new state."
30
+ }
31
+ },
32
+ required: ["command"]
33
+ }
34
+ end
35
+
36
+ def available?
37
+ # Available in IRB mode (not in active debug session)
38
+ # DEBUGGER__::SESSION indicates an active debug session
39
+ defined?(IRB) && !defined?(DEBUGGER__::SESSION)
40
+ end
41
+ end
42
+
43
+ def execute(binding, command:, auto_continue: false)
44
+ Girb::IrbIntegration.add_pending_irb_command(command)
45
+ Girb::AutoContinue.request! if auto_continue
46
+
47
+ {
48
+ success: true,
49
+ command: command,
50
+ auto_continue: auto_continue,
51
+ message: auto_continue ?
52
+ "Command '#{command}' will be executed. You will be re-invoked with updated context." :
53
+ "Command '#{command}' will be executed after this response."
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative "base"
4
4
  require_relative "../session_history"
5
+ require_relative "../conversation_history"
6
+ require_relative "../session_persistence"
5
7
 
6
8
  module Girb
7
9
  module Tools
@@ -12,7 +14,13 @@ module Girb
12
14
  end
13
15
 
14
16
  def description
15
- "Get IRB session history. Can retrieve specific lines, line ranges, method definitions, AI conversation details, or full history."
17
+ "Get IRB session history including AI conversations from previous sessions (if persisted). " \
18
+ "Can retrieve specific lines, line ranges, method definitions, AI conversation details, or full history."
19
+ end
20
+
21
+ def available?
22
+ # Only available in IRB mode, not in debug mode
23
+ !defined?(DEBUGGER__)
16
24
  end
17
25
 
18
26
  def parameters
@@ -146,21 +154,51 @@ module Girb
146
154
  end
147
155
 
148
156
  def list_ai_conversations
149
- conversations = SessionHistory.ai_conversations
150
- if conversations.any?
157
+ all_conversations = []
158
+
159
+ # 永続化されたセッションからの会話
160
+ persisted = get_persisted_conversations
161
+ all_conversations.concat(persisted)
162
+
163
+ # 現在のセッションの会話
164
+ current = SessionHistory.ai_conversations.map do |c|
151
165
  {
152
- count: conversations.size,
153
- conversations: conversations.map do |c|
166
+ line: c[:line_no],
167
+ question: c[:question],
168
+ response: c[:response] || "",
169
+ source: "current_session"
170
+ }
171
+ end
172
+ all_conversations.concat(current)
173
+
174
+ if all_conversations.any?
175
+ {
176
+ count: all_conversations.size,
177
+ conversations: all_conversations.map do |c|
178
+ response = c[:response] || ""
154
179
  {
155
- line: c[:line_no],
180
+ line: c[:line],
156
181
  question: c[:question],
157
- response_preview: c[:response][0, 200] + (c[:response].length > 200 ? "..." : "")
182
+ response_preview: response.length > 200 ? "#{response[0, 200]}..." : response,
183
+ source: c[:source] || "persisted"
158
184
  }
159
185
  end
160
186
  }
161
187
  else
162
- { message: "No AI conversations in this session" }
188
+ { message: "No AI conversations in session history" }
189
+ end
190
+ end
191
+
192
+ def get_persisted_conversations
193
+ conversations = []
194
+ ConversationHistory.messages.each do |msg|
195
+ if msg.role == "user"
196
+ conversations << { question: msg.content, source: "persisted" }
197
+ elsif msg.role == "model" && conversations.last && !conversations.last[:response]
198
+ conversations.last[:response] = msg.content
199
+ end
163
200
  end
201
+ conversations
164
202
  end
165
203
 
166
204
  def get_ai_detail(line)
data/lib/girb/tools.rb CHANGED
@@ -8,15 +8,36 @@ require_relative "tools/evaluate_code"
8
8
  require_relative "tools/read_file"
9
9
  require_relative "tools/find_file"
10
10
  require_relative "tools/session_history_tool"
11
+ require_relative "tools/debug_session_history_tool"
11
12
  require_relative "tools/environment_tools"
13
+ require_relative "tools/continue_analysis"
14
+ require_relative "tools/run_irb_debug_command"
12
15
 
13
16
  module Girb
14
17
  module Tools
15
- CORE_TOOLS = [InspectObject, GetSource, ListMethods, EvaluateCode, ReadFile, FindFile, SessionHistoryTool, GetCurrentDirectory].freeze
18
+ # Shared tools available in both IRB and debug modes
19
+ SHARED_TOOLS = [InspectObject, GetSource, ListMethods, EvaluateCode, ReadFile, FindFile, GetCurrentDirectory].freeze
20
+
21
+ # IRB-only tools
22
+ IRB_TOOLS = [SessionHistoryTool, ContinueAnalysis, RunIrbDebugCommand].freeze
23
+
24
+ # Debug-only tools (RunDebugCommand is registered separately in DebugIntegration)
25
+ DEBUG_TOOLS = [DebugSessionHistoryTool].freeze
26
+
27
+ # All core tools (used for backward compatibility)
28
+ CORE_TOOLS = (SHARED_TOOLS + IRB_TOOLS + DEBUG_TOOLS).freeze
16
29
 
17
30
  class << self
31
+ def registered_tools
32
+ @registered_tools ||= []
33
+ end
34
+
35
+ def register(tool_class)
36
+ registered_tools << tool_class unless registered_tools.include?(tool_class)
37
+ end
38
+
18
39
  def available_tools
19
- tools = CORE_TOOLS.dup
40
+ tools = CORE_TOOLS.dup + registered_tools
20
41
 
21
42
  # Rails tools are loaded conditionally
22
43
  if defined?(Rails)
data/lib/girb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Girb
4
- VERSION = "0.1.2"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/girb.rb CHANGED
@@ -17,7 +17,7 @@ module Girb
17
17
  class ApiError < Error; end
18
18
 
19
19
  class << self
20
- attr_accessor :configuration
20
+ attr_accessor :configuration, :debug_session
21
21
 
22
22
  def configure
23
23
  self.configuration ||= Configuration.new
@@ -41,17 +41,34 @@ end
41
41
  # Rails がロードされていたら Railtie を組み込む
42
42
  require_relative "girb/railtie" if defined?(Rails::Railtie)
43
43
 
44
+ # debug gem がロードされていたら DebugIntegration を組み込む
45
+ if defined?(DEBUGGER__)
46
+ Girb.configure unless Girb.configuration
47
+ Girb::GirbrcLoader.load_girbrc
48
+ require_relative "girb/debug_integration"
49
+ Girb::DebugIntegration.setup
50
+ end
51
+
44
52
  # binding.girb サポート
45
53
  class Binding
46
- def girb
54
+ def girb(show_code: true)
47
55
  require "irb"
48
56
  Girb.setup! unless defined?(IRB::Command::Qq)
49
57
 
50
- # IRB.start with this binding
51
- IRB.setup(source_location[0], argv: [])
58
+ # .girbrcを読み込む(プロバイダー設定)
59
+ Girb::GirbrcLoader.load_girbrc unless Girb.configuration&.provider
60
+
61
+ # キーバインドを再設定(IRBセッション開始前に確実に設定)
62
+ Girb::IrbIntegration.install_ai_keybinding
63
+
64
+ # 標準のbinding.irbと同じ方法でIRBを起動
65
+ IRB.setup(source_location[0], argv: []) unless IRB.initialized?
52
66
  workspace = IRB::WorkSpace.new(self)
53
- irb = IRB::Irb.new(workspace)
54
- IRB.conf[:MAIN_CONTEXT] = irb.context
55
- irb.run(IRB.conf)
67
+ STDOUT.print(workspace.code_around_binding) if show_code
68
+
69
+ binding_irb = IRB::Irb.new(workspace, from_binding: true)
70
+ binding_irb.context.irb_path = File.expand_path(source_location[0])
71
+ binding_irb.run(IRB.conf)
72
+ binding_irb.debug_break
56
73
  end
57
74
  end
@@ -12,17 +12,29 @@ module IRB
12
12
  question = question.to_s.strip
13
13
  if question.empty?
14
14
  puts "[girb] Usage: qq \"your question here\""
15
+ puts "[girb] qq session clear - Clear current session"
16
+ puts "[girb] qq session list - List saved sessions"
15
17
  return
16
18
  end
17
19
 
18
- unless Girb.configuration&.gemini_api_key
19
- puts "[girb] Error: GEMINI_API_KEY not set"
20
- puts "[girb] Run: export GEMINI_API_KEY=your-key"
21
- puts "[girb] Or configure in your .irbrc:"
22
- puts "[girb] Girb.configure { |c| c.gemini_api_key = 'your-key' }"
20
+ # セッション管理コマンド
21
+ if question.start_with?("session ")
22
+ handle_session_command(question.sub(/^session\s+/, ""))
23
23
  return
24
24
  end
25
25
 
26
+ unless Girb.configuration&.provider
27
+ puts "[girb] Error: No LLM provider configured."
28
+ puts "[girb] Install a provider gem and configure it:"
29
+ puts "[girb] gem install girb-ruby_llm"
30
+ puts "[girb] export GIRB_MODEL=gemini-2.5-flash"
31
+ puts "[girb] export GEMINI_API_KEY=your-key"
32
+ return
33
+ end
34
+
35
+ # セッションが有効なら開始
36
+ Girb::IrbIntegration.start_session! if Girb::SessionPersistence.enabled?
37
+
26
38
  current_binding = irb_context.workspace.binding
27
39
 
28
40
  # AI質問を履歴に記録
@@ -41,13 +53,43 @@ module IRB
41
53
  end
42
54
 
43
55
  client = Girb::AiClient.new
44
- client.ask(question, context, binding: current_binding, line_no: line_no)
56
+ client.ask(question, context, binding: current_binding, line_no: line_no, irb_context: irb_context)
45
57
  rescue Girb::Error => e
46
58
  puts "[girb] Error: #{e.message}"
47
59
  rescue StandardError => e
48
60
  puts "[girb] Error: #{e.message}"
49
61
  puts e.backtrace.first(5).join("\n") if Girb.configuration&.debug
50
62
  end
63
+
64
+ private
65
+
66
+ def handle_session_command(cmd)
67
+ case cmd.strip
68
+ when "clear"
69
+ Girb::SessionPersistence.clear_session
70
+ when "list"
71
+ sessions = Girb::SessionPersistence.list_sessions
72
+ if sessions.empty?
73
+ puts "[girb] No saved sessions"
74
+ else
75
+ puts "[girb] Saved sessions:"
76
+ sessions.each do |s|
77
+ puts " - #{s[:id]} (#{s[:message_count]} messages, saved: #{s[:saved_at]})"
78
+ end
79
+ end
80
+ when "status"
81
+ if Girb::SessionPersistence.current_session_id
82
+ puts "[girb] Current session: #{Girb::SessionPersistence.current_session_id}"
83
+ elsif Girb.debug_session
84
+ puts "[girb] Session configured: #{Girb.debug_session} (not started)"
85
+ else
86
+ puts "[girb] No session configured (use Girb.debug_session = 'name' to enable)"
87
+ end
88
+ else
89
+ puts "[girb] Unknown session command: #{cmd}"
90
+ puts "[girb] Available: clear, list, status"
91
+ end
92
+ end
51
93
  end
52
94
  end
53
95
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: girb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - rira100000000
@@ -84,9 +84,14 @@ files:
84
84
  - exe/girb
85
85
  - lib/girb.rb
86
86
  - lib/girb/ai_client.rb
87
+ - lib/girb/auto_continue.rb
87
88
  - lib/girb/configuration.rb
88
89
  - lib/girb/context_builder.rb
89
90
  - lib/girb/conversation_history.rb
91
+ - lib/girb/debug_context_builder.rb
92
+ - lib/girb/debug_integration.rb
93
+ - lib/girb/debug_prompt_builder.rb
94
+ - lib/girb/debug_session_history.rb
90
95
  - lib/girb/exception_capture.rb
91
96
  - lib/girb/girbrc_loader.rb
92
97
  - lib/girb/irb_integration.rb
@@ -94,8 +99,11 @@ files:
94
99
  - lib/girb/providers/base.rb
95
100
  - lib/girb/railtie.rb
96
101
  - lib/girb/session_history.rb
102
+ - lib/girb/session_persistence.rb
97
103
  - lib/girb/tools.rb
98
104
  - lib/girb/tools/base.rb
105
+ - lib/girb/tools/continue_analysis.rb
106
+ - lib/girb/tools/debug_session_history_tool.rb
99
107
  - lib/girb/tools/environment_tools.rb
100
108
  - lib/girb/tools/evaluate_code.rb
101
109
  - lib/girb/tools/find_file.rb
@@ -104,6 +112,8 @@ files:
104
112
  - lib/girb/tools/list_methods.rb
105
113
  - lib/girb/tools/rails_tools.rb
106
114
  - lib/girb/tools/read_file.rb
115
+ - lib/girb/tools/run_debug_command.rb
116
+ - lib/girb/tools/run_irb_debug_command.rb
107
117
  - lib/girb/tools/session_history_tool.rb
108
118
  - lib/girb/version.rb
109
119
  - lib/irb/command/qq.rb