girb 0.2.0 → 0.3.1
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 +4 -4
- data/CHANGELOG.md +56 -1
- data/README.md +271 -154
- data/README_ja.md +268 -151
- data/lib/girb/ai_client.rb +23 -5
- data/lib/girb/auto_continue.rb +1 -1
- data/lib/girb/conversation_history.rb +12 -7
- data/lib/girb/debug_integration.rb +158 -10
- data/lib/girb/debug_prompt_builder.rb +71 -11
- data/lib/girb/exception_capture.rb +4 -0
- data/lib/girb/irb_integration.rb +232 -0
- data/lib/girb/prompt_builder.rb +128 -51
- data/lib/girb/session_persistence.rb +170 -0
- data/lib/girb/tools/debug_session_history_tool.rb +61 -18
- data/lib/girb/tools/run_irb_debug_command.rb +58 -0
- data/lib/girb/tools/session_history_tool.rb +41 -8
- data/lib/girb/tools.rb +2 -1
- data/lib/girb/version.rb +1 -1
- data/lib/girb.rb +16 -7
- data/lib/irb/command/qq.rb +41 -0
- metadata +3 -1
data/lib/girb/prompt_builder.rb
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Girb
|
|
4
4
|
class PromptBuilder
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
# Common prompt shared across all IRB modes
|
|
6
|
+
COMMON_PROMPT = <<~PROMPT
|
|
7
|
+
You are girb, an AI assistant embedded in a Ruby developer's session.
|
|
7
8
|
|
|
8
9
|
## CRITICAL: Prompt Information Takes Highest Priority
|
|
9
10
|
Information in this system prompt and "User-Defined Instructions" section
|
|
@@ -14,25 +15,6 @@ module Girb
|
|
|
14
15
|
## Language
|
|
15
16
|
Respond in the same language the user is using. Detect the user's language from their question and match it.
|
|
16
17
|
|
|
17
|
-
## Important: Understand the IRB Session Context
|
|
18
|
-
The user is interactively executing code in IRB and asking questions within that flow.
|
|
19
|
-
"Session History" contains the code the user has executed and past AI conversations in chronological order.
|
|
20
|
-
Always interpret questions in the context of this history.
|
|
21
|
-
|
|
22
|
-
For example, if the history shows:
|
|
23
|
-
1: a = 1
|
|
24
|
-
2: b = 2
|
|
25
|
-
3: [USER] What will z be if I continue with c = 3 and beyond?
|
|
26
|
-
The user is asking about the value of z when continuing the pattern a=1, b=2, c=3... (answer: z=26).
|
|
27
|
-
|
|
28
|
-
## Your Role
|
|
29
|
-
- Strive to understand the user's true intent and background
|
|
30
|
-
- Don't just answer the question; understand what they're trying to achieve and what challenges they face
|
|
31
|
-
- Analyze session history to understand what the user is trying to do
|
|
32
|
-
- Utilize the current execution context (variables, object state, exceptions)
|
|
33
|
-
- Provide specific, practical answers to questions
|
|
34
|
-
- Use tools to execute and verify code as needed
|
|
35
|
-
|
|
36
18
|
## Clarifying Questions (Use Sparingly)
|
|
37
19
|
Only ask the user for clarification AFTER you have already investigated using tools.
|
|
38
20
|
- First: read the source file, check variables, run code
|
|
@@ -41,7 +23,7 @@ module Girb
|
|
|
41
23
|
## Response Guidelines
|
|
42
24
|
- Keep responses concise and practical
|
|
43
25
|
- Read patterns and intentions; handle hypothetical questions
|
|
44
|
-
- Code examples should use variables and objects from the current
|
|
26
|
+
- Code examples should use variables and objects from the current context and be directly executable
|
|
45
27
|
|
|
46
28
|
## Debugging Support on Errors
|
|
47
29
|
When users encounter errors, actively support debugging.
|
|
@@ -51,9 +33,6 @@ module Girb
|
|
|
51
33
|
|
|
52
34
|
## CRITICAL: Proactive Investigation — Act First, Don't Ask
|
|
53
35
|
You MUST investigate before asking the user for information.
|
|
54
|
-
- The "Source Location" in the context tells you which file the user is working in.
|
|
55
|
-
If a Source Location is present, ALWAYS use `read_file` to read that file FIRST
|
|
56
|
-
before responding. The user's question almost certainly refers to this file's code.
|
|
57
36
|
- Use `evaluate_code` to run and verify code rather than guessing or reasoning about results.
|
|
58
37
|
- NEVER ask the user for code, file names, or variable definitions that you can look up
|
|
59
38
|
yourself with `read_file`, `evaluate_code`, `inspect_object`, or `find_file`.
|
|
@@ -61,7 +40,92 @@ module Girb
|
|
|
61
40
|
## Available Tools
|
|
62
41
|
Use tools to inspect variables in detail, retrieve source code, and execute code.
|
|
63
42
|
Actively use the evaluate_code tool especially for verifying hypotheses and calculations.
|
|
43
|
+
PROMPT
|
|
64
44
|
|
|
45
|
+
# Prompt specific to breakpoint mode (binding.girb / binding.irb)
|
|
46
|
+
BREAKPOINT_PROMPT = <<~PROMPT
|
|
47
|
+
## Mode: Breakpoint (binding.girb)
|
|
48
|
+
You are at a BREAKPOINT in the user's Ruby script. Execution is paused at this exact line.
|
|
49
|
+
|
|
50
|
+
### CRITICAL: Understanding Breakpoint Context
|
|
51
|
+
- Code BEFORE this line has already executed (variables are set)
|
|
52
|
+
- Code AFTER this line has NOT executed yet
|
|
53
|
+
- The user's questions about "the code", "this loop", "this method" refer to the code in the SOURCE FILE
|
|
54
|
+
- ALWAYS read the source file FIRST using `read_file` to understand what code exists
|
|
55
|
+
|
|
56
|
+
### Your Primary Task
|
|
57
|
+
- Help the user understand, debug, or simulate the code that is ABOUT TO execute
|
|
58
|
+
- When asked to "run the code" or "execute this loop", execute the ACTUAL code from the file
|
|
59
|
+
- When asked to track variables, run the actual code and report real results
|
|
60
|
+
- NEVER invent or substitute code - always use what's in the file
|
|
61
|
+
|
|
62
|
+
### Example: User says "run this loop and track x"
|
|
63
|
+
1. Read the source file to see the actual loop code
|
|
64
|
+
2. Execute that exact code using evaluate_code
|
|
65
|
+
3. Report the actual results
|
|
66
|
+
|
|
67
|
+
### WRONG approach:
|
|
68
|
+
- Guessing what the code might do
|
|
69
|
+
- Writing your own version of the code
|
|
70
|
+
- Asking the user what the code is when you can read the file
|
|
71
|
+
|
|
72
|
+
### Debug Commands (use run_debug_command tool)
|
|
73
|
+
IRB integrates with debug gem. Use `run_debug_command` tool to execute:
|
|
74
|
+
- `next` / `n`: Step over to next line
|
|
75
|
+
- `step` / `s`: Step into method calls
|
|
76
|
+
- `continue` / `c`: Continue execution
|
|
77
|
+
- `finish`: Run until current method returns
|
|
78
|
+
- `break <file>:<line>`: Set a breakpoint
|
|
79
|
+
- `backtrace` / `bt`: Show call stack
|
|
80
|
+
- `info`: Show local variables
|
|
81
|
+
|
|
82
|
+
When the user asks for step-by-step execution, use `run_debug_command` with `auto_continue: true`
|
|
83
|
+
to step through the code and be re-invoked to see the results.
|
|
84
|
+
PROMPT
|
|
85
|
+
|
|
86
|
+
# Prompt specific to interactive IRB mode (girb command)
|
|
87
|
+
INTERACTIVE_IRB_PROMPT = <<~PROMPT
|
|
88
|
+
## Mode: Interactive IRB Session
|
|
89
|
+
The user is in an interactive IRB session, typing code and questions directly.
|
|
90
|
+
|
|
91
|
+
### Understanding the Session
|
|
92
|
+
- "Session History" contains the code the user has executed and past AI conversations
|
|
93
|
+
- Always interpret questions in the context of this history
|
|
94
|
+
- Variables and objects from past commands are available in the current context
|
|
95
|
+
|
|
96
|
+
### Example Context
|
|
97
|
+
If the history shows:
|
|
98
|
+
1: a = 1
|
|
99
|
+
2: b = 2
|
|
100
|
+
3: [USER] What will z be if I continue with c = 3 and beyond?
|
|
101
|
+
The user is asking about the value of z when continuing the pattern a=1, b=2, c=3... (answer: z=26).
|
|
102
|
+
|
|
103
|
+
### Your Role
|
|
104
|
+
- Help with code exploration and experimentation
|
|
105
|
+
- Answer questions about Ruby, gems, and the current session state
|
|
106
|
+
- Assist with building and testing code interactively
|
|
107
|
+
PROMPT
|
|
108
|
+
|
|
109
|
+
# Prompt specific to Rails console mode
|
|
110
|
+
RAILS_CONSOLE_PROMPT = <<~PROMPT
|
|
111
|
+
## Mode: Rails Console
|
|
112
|
+
The user is in a Rails console with full access to the application's models and services.
|
|
113
|
+
|
|
114
|
+
### Rails-Specific Capabilities
|
|
115
|
+
- You can query ActiveRecord models directly
|
|
116
|
+
- Use `model_info` tool to get schema information
|
|
117
|
+
- Use `query_model` tool to execute database queries safely
|
|
118
|
+
- Access to Rails helpers, routes, and application configuration
|
|
119
|
+
|
|
120
|
+
### Best Practices
|
|
121
|
+
- Be careful with destructive operations (update!, destroy, etc.) - warn the user
|
|
122
|
+
- Use transactions when demonstrating data modifications
|
|
123
|
+
- Suggest using `find_by` or `where` instead of `find` to avoid exceptions
|
|
124
|
+
- Remember that console changes affect the real database (unless in sandbox mode)
|
|
125
|
+
PROMPT
|
|
126
|
+
|
|
127
|
+
# Autonomous investigation prompt (shared)
|
|
128
|
+
CONTINUE_ANALYSIS_PROMPT = <<~PROMPT
|
|
65
129
|
## Autonomous Investigation with continue_analysis
|
|
66
130
|
When you need to execute code that changes state AND then see the full updated context
|
|
67
131
|
(all local variables, instance variables, last value, etc.), use the `continue_analysis` tool.
|
|
@@ -78,12 +142,6 @@ module Girb
|
|
|
78
142
|
- When you can get the information you need with evaluate_code or inspect_object directly
|
|
79
143
|
- When you've found your answer and want to report to the user
|
|
80
144
|
- For simple one-shot investigations
|
|
81
|
-
|
|
82
|
-
### Example workflow:
|
|
83
|
-
1. evaluate_code("user.profile.update!(name: 'test')") → check if it succeeds
|
|
84
|
-
2. continue_analysis(reason: "Check all updated attributes after save")
|
|
85
|
-
3. [re-invoked with fresh context showing all updated locals/instance vars]
|
|
86
|
-
4. Analyze the changes and report to the user
|
|
87
145
|
PROMPT
|
|
88
146
|
|
|
89
147
|
def initialize(question, context)
|
|
@@ -105,18 +163,20 @@ module Girb
|
|
|
105
163
|
|
|
106
164
|
# System prompt (shared across conversation)
|
|
107
165
|
def system_prompt
|
|
166
|
+
prompt = COMMON_PROMPT + "\n" + mode_specific_prompt + "\n" + CONTINUE_ANALYSIS_PROMPT
|
|
167
|
+
|
|
108
168
|
custom = Girb.configuration&.custom_prompt
|
|
109
169
|
if custom && !custom.empty?
|
|
110
|
-
"
|
|
170
|
+
prompt + "\n\n## User-Defined Instructions\n#{custom}"
|
|
111
171
|
else
|
|
112
|
-
|
|
172
|
+
prompt
|
|
113
173
|
end
|
|
114
174
|
end
|
|
115
175
|
|
|
116
176
|
# User message (context + question)
|
|
117
177
|
def user_message
|
|
118
178
|
<<~MSG
|
|
119
|
-
## Current
|
|
179
|
+
## Current Context
|
|
120
180
|
#{build_context_section}
|
|
121
181
|
|
|
122
182
|
## Question
|
|
@@ -126,32 +186,49 @@ module Girb
|
|
|
126
186
|
|
|
127
187
|
private
|
|
128
188
|
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
189
|
+
def mode_specific_prompt
|
|
190
|
+
case detect_mode
|
|
191
|
+
when :breakpoint
|
|
192
|
+
BREAKPOINT_PROMPT
|
|
193
|
+
when :rails
|
|
194
|
+
RAILS_CONSOLE_PROMPT
|
|
195
|
+
else
|
|
196
|
+
INTERACTIVE_IRB_PROMPT
|
|
197
|
+
end
|
|
198
|
+
end
|
|
133
199
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
#{format_session_history}
|
|
200
|
+
def detect_mode
|
|
201
|
+
loc = @context[:source_location]
|
|
137
202
|
|
|
138
|
-
|
|
139
|
-
|
|
203
|
+
# Check for breakpoint mode: source is a real file (not irb/eval)
|
|
204
|
+
if loc && loc[:file]
|
|
205
|
+
file = loc[:file].to_s
|
|
206
|
+
unless file.start_with?("(") || file.include?("irb") || file.include?("eval")
|
|
207
|
+
return :breakpoint
|
|
208
|
+
end
|
|
209
|
+
end
|
|
140
210
|
|
|
141
|
-
|
|
142
|
-
|
|
211
|
+
# Check for Rails mode
|
|
212
|
+
return :rails if defined?(Rails)
|
|
143
213
|
|
|
144
|
-
|
|
145
|
-
|
|
214
|
+
# Default: interactive IRB
|
|
215
|
+
:interactive
|
|
216
|
+
end
|
|
146
217
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
218
|
+
def build_context_section
|
|
219
|
+
sections = []
|
|
220
|
+
sections << "### Source Location\n#{format_source_location}"
|
|
221
|
+
sections << "### Session History (Previous Inputs)\n#{format_session_history}"
|
|
222
|
+
sections << "### Current Local Variables\n#{format_locals}"
|
|
223
|
+
sections << "### Last Evaluation Result\n#{@context[:last_value] || "(none)"}"
|
|
224
|
+
sections << "### Last Exception\n#{format_exception}"
|
|
225
|
+
sections << "### Methods Defined in Session\n#{format_method_definitions}"
|
|
226
|
+
sections.join("\n\n")
|
|
150
227
|
end
|
|
151
228
|
|
|
152
229
|
def format_source_location
|
|
153
230
|
loc = @context[:source_location]
|
|
154
|
-
return "(
|
|
231
|
+
return "(interactive session)" unless loc
|
|
155
232
|
|
|
156
233
|
"File: #{loc[:file]}\nLine: #{loc[:line]}"
|
|
157
234
|
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Girb
|
|
7
|
+
# デバッグセッションの会話履歴を永続化するクラス
|
|
8
|
+
# 明示的にGirb.debug_sessionを設定した場合のみ保存される
|
|
9
|
+
class SessionPersistence
|
|
10
|
+
SESSIONS_DIR = ".girb/sessions"
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
attr_accessor :current_session_id
|
|
14
|
+
|
|
15
|
+
# セッションが有効か(明示的にIDが指定されているか)
|
|
16
|
+
def enabled?
|
|
17
|
+
!!Girb.debug_session
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# セッションディレクトリを取得(プロジェクトルートから)
|
|
21
|
+
def sessions_dir
|
|
22
|
+
# カレントディレクトリから.girbディレクトリを探す
|
|
23
|
+
dir = Dir.pwd
|
|
24
|
+
while dir != "/"
|
|
25
|
+
girb_dir = File.join(dir, ".girb")
|
|
26
|
+
if Dir.exist?(girb_dir)
|
|
27
|
+
return File.join(girb_dir, "sessions")
|
|
28
|
+
end
|
|
29
|
+
dir = File.dirname(dir)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# 見つからなければカレントディレクトリに作成
|
|
33
|
+
File.join(Dir.pwd, SESSIONS_DIR)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# セッションファイルのパスを取得
|
|
37
|
+
def session_file_path(session_id)
|
|
38
|
+
File.join(sessions_dir, "#{session_id}.json")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# セッションを開始(既存があれば読み込み)
|
|
42
|
+
# Girb.debug_sessionが設定されている場合のみ有効
|
|
43
|
+
def start_session
|
|
44
|
+
return unless enabled?
|
|
45
|
+
|
|
46
|
+
@current_session_id = Girb.debug_session
|
|
47
|
+
|
|
48
|
+
file_path = session_file_path(@current_session_id)
|
|
49
|
+
if File.exist?(file_path)
|
|
50
|
+
load_session(file_path)
|
|
51
|
+
puts "[girb] Resumed session: #{@current_session_id}"
|
|
52
|
+
else
|
|
53
|
+
ConversationHistory.reset!
|
|
54
|
+
puts "[girb] New session: #{@current_session_id}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@current_session_id
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# セッションを保存
|
|
61
|
+
def save_session
|
|
62
|
+
return unless enabled? && @current_session_id
|
|
63
|
+
|
|
64
|
+
file_path = session_file_path(@current_session_id)
|
|
65
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
|
66
|
+
|
|
67
|
+
data = {
|
|
68
|
+
session_id: @current_session_id,
|
|
69
|
+
saved_at: Time.now.to_s,
|
|
70
|
+
messages: serialize_messages
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
File.write(file_path, JSON.pretty_generate(data))
|
|
74
|
+
rescue => e
|
|
75
|
+
puts "[girb] Failed to save session: #{e.message}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# セッションを読み込み
|
|
79
|
+
def load_session(file_path)
|
|
80
|
+
data = JSON.parse(File.read(file_path), symbolize_names: true)
|
|
81
|
+
|
|
82
|
+
ConversationHistory.reset!
|
|
83
|
+
deserialize_messages(data[:messages])
|
|
84
|
+
|
|
85
|
+
message_count = data[:messages]&.size || 0
|
|
86
|
+
puts "[girb] Loaded #{message_count} messages from previous session"
|
|
87
|
+
rescue => e
|
|
88
|
+
puts "[girb] Failed to load session: #{e.message}"
|
|
89
|
+
ConversationHistory.reset!
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# 現在のセッションをクリア(ファイルも削除)
|
|
93
|
+
def clear_session
|
|
94
|
+
if @current_session_id
|
|
95
|
+
delete_session(@current_session_id)
|
|
96
|
+
@current_session_id = nil
|
|
97
|
+
end
|
|
98
|
+
ConversationHistory.reset!
|
|
99
|
+
puts "[girb] Session cleared"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# セッション一覧を取得
|
|
103
|
+
def list_sessions
|
|
104
|
+
dir = sessions_dir
|
|
105
|
+
return [] unless Dir.exist?(dir)
|
|
106
|
+
|
|
107
|
+
Dir.glob(File.join(dir, "*.json")).map do |file|
|
|
108
|
+
data = JSON.parse(File.read(file), symbolize_names: true)
|
|
109
|
+
{
|
|
110
|
+
id: data[:session_id],
|
|
111
|
+
saved_at: data[:saved_at],
|
|
112
|
+
message_count: data[:messages]&.size || 0
|
|
113
|
+
}
|
|
114
|
+
rescue
|
|
115
|
+
nil
|
|
116
|
+
end.compact
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# セッションを削除
|
|
120
|
+
def delete_session(session_id)
|
|
121
|
+
file_path = session_file_path(session_id)
|
|
122
|
+
if File.exist?(file_path)
|
|
123
|
+
File.delete(file_path)
|
|
124
|
+
puts "[girb] Session deleted: #{session_id}"
|
|
125
|
+
true
|
|
126
|
+
else
|
|
127
|
+
puts "[girb] Session not found: #{session_id}"
|
|
128
|
+
false
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def serialize_messages
|
|
135
|
+
ConversationHistory.messages.map do |msg|
|
|
136
|
+
{
|
|
137
|
+
role: msg.role,
|
|
138
|
+
content: msg.content,
|
|
139
|
+
tool_calls: msg.tool_calls
|
|
140
|
+
}
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def deserialize_messages(messages)
|
|
145
|
+
return unless messages
|
|
146
|
+
|
|
147
|
+
messages.each do |msg|
|
|
148
|
+
case msg[:role]
|
|
149
|
+
when "user"
|
|
150
|
+
ConversationHistory.add_user_message(msg[:content])
|
|
151
|
+
when "model"
|
|
152
|
+
# tool_callsがある場合は先にpending_tool_callsに追加
|
|
153
|
+
if msg[:tool_calls]&.any?
|
|
154
|
+
msg[:tool_calls].each do |tc|
|
|
155
|
+
ConversationHistory.add_tool_call(
|
|
156
|
+
tc[:name],
|
|
157
|
+
tc[:args],
|
|
158
|
+
tc[:result],
|
|
159
|
+
id: tc[:id],
|
|
160
|
+
metadata: tc[:metadata]
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
ConversationHistory.add_assistant_message(msg[:content])
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "base"
|
|
4
4
|
require_relative "../debug_session_history"
|
|
5
|
+
require_relative "../conversation_history"
|
|
6
|
+
require_relative "../session_persistence"
|
|
5
7
|
|
|
6
8
|
module Girb
|
|
7
9
|
module Tools
|
|
@@ -12,8 +14,8 @@ module Girb
|
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def description
|
|
15
|
-
"Get
|
|
16
|
-
"
|
|
17
|
+
"Get session history including AI conversations from previous sessions (if persisted) and current session commands. " \
|
|
18
|
+
"Use this to recall past conversations and debug commands."
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def parameters
|
|
@@ -44,7 +46,7 @@ module Girb
|
|
|
44
46
|
when "full_history"
|
|
45
47
|
get_full_history(count)
|
|
46
48
|
when "list_ai_conversations"
|
|
47
|
-
list_ai_conversations
|
|
49
|
+
list_ai_conversations(count)
|
|
48
50
|
else
|
|
49
51
|
{ error: "Unknown action: #{action}. Use 'full_history' or 'list_ai_conversations'." }
|
|
50
52
|
end
|
|
@@ -55,34 +57,75 @@ module Girb
|
|
|
55
57
|
private
|
|
56
58
|
|
|
57
59
|
def get_full_history(count)
|
|
60
|
+
result = {}
|
|
61
|
+
|
|
62
|
+
# 永続化されたセッションからの会話履歴
|
|
63
|
+
persisted = get_persisted_conversations
|
|
64
|
+
if persisted.any?
|
|
65
|
+
result[:persisted_conversations] = persisted
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# 現在のセッションのデバッグ履歴
|
|
58
69
|
history = DebugSessionHistory.format_history(count)
|
|
59
70
|
if history && !history.empty?
|
|
60
|
-
|
|
71
|
+
result[:current_session_history] = history
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if result.empty?
|
|
75
|
+
{ message: "No history available" }
|
|
61
76
|
else
|
|
62
|
-
|
|
77
|
+
result
|
|
63
78
|
end
|
|
64
79
|
end
|
|
65
80
|
|
|
66
|
-
def list_ai_conversations
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
def list_ai_conversations(count = 20)
|
|
82
|
+
all_conversations = []
|
|
83
|
+
|
|
84
|
+
# 永続化されたセッションからの会話
|
|
85
|
+
persisted = get_persisted_conversations
|
|
86
|
+
all_conversations.concat(persisted)
|
|
87
|
+
|
|
88
|
+
# 現在のセッションの会話
|
|
89
|
+
current = DebugSessionHistory.ai_conversations.map do |c|
|
|
69
90
|
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
91
|
+
question: c.content,
|
|
92
|
+
response: c.response || "(pending)",
|
|
93
|
+
source: "current_session"
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
all_conversations.concat(current)
|
|
97
|
+
|
|
98
|
+
if all_conversations.any?
|
|
99
|
+
# 最新のcount件に制限
|
|
100
|
+
limited = all_conversations.last(count)
|
|
101
|
+
{
|
|
102
|
+
total_count: all_conversations.size,
|
|
103
|
+
showing: limited.size,
|
|
104
|
+
conversations: limited.map do |c|
|
|
105
|
+
response = c[:response] || ""
|
|
106
|
+
response_preview = response.length > 200 ? "#{response[0, 200]}..." : response
|
|
77
107
|
{
|
|
78
|
-
question: c
|
|
79
|
-
response_preview: response_preview
|
|
108
|
+
question: c[:question],
|
|
109
|
+
response_preview: response_preview,
|
|
110
|
+
source: c[:source] || "persisted"
|
|
80
111
|
}
|
|
81
112
|
end
|
|
82
113
|
}
|
|
83
114
|
else
|
|
84
|
-
{ message: "No AI conversations in
|
|
115
|
+
{ message: "No AI conversations in session history" }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def get_persisted_conversations
|
|
120
|
+
conversations = []
|
|
121
|
+
ConversationHistory.messages.each do |msg|
|
|
122
|
+
if msg.role == "user"
|
|
123
|
+
conversations << { question: msg.content, source: "persisted" }
|
|
124
|
+
elsif msg.role == "model" && conversations.last && !conversations.last[:response]
|
|
125
|
+
conversations.last[:response] = msg.content
|
|
126
|
+
end
|
|
85
127
|
end
|
|
128
|
+
conversations
|
|
86
129
|
end
|
|
87
130
|
end
|
|
88
131
|
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,8 @@ module Girb
|
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def description
|
|
15
|
-
"Get IRB session 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."
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def available?
|
|
@@ -151,21 +154,51 @@ module Girb
|
|
|
151
154
|
end
|
|
152
155
|
|
|
153
156
|
def list_ai_conversations
|
|
154
|
-
|
|
155
|
-
|
|
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|
|
|
156
165
|
{
|
|
157
|
-
|
|
158
|
-
|
|
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] || ""
|
|
159
179
|
{
|
|
160
|
-
line: c[:
|
|
180
|
+
line: c[:line],
|
|
161
181
|
question: c[:question],
|
|
162
|
-
response_preview:
|
|
182
|
+
response_preview: response.length > 200 ? "#{response[0, 200]}..." : response,
|
|
183
|
+
source: c[:source] || "persisted"
|
|
163
184
|
}
|
|
164
185
|
end
|
|
165
186
|
}
|
|
166
187
|
else
|
|
167
|
-
{ message: "No AI conversations in
|
|
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
|
|
168
200
|
end
|
|
201
|
+
conversations
|
|
169
202
|
end
|
|
170
203
|
|
|
171
204
|
def get_ai_detail(line)
|