girb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE +21 -0
- data/README.md +253 -0
- data/README_ja.md +251 -0
- data/Rakefile +8 -0
- data/exe/girb +38 -0
- data/lib/girb/ai_client.rb +157 -0
- data/lib/girb/configuration.rb +39 -0
- data/lib/girb/context_builder.rb +134 -0
- data/lib/girb/conversation_history.rb +132 -0
- data/lib/girb/exception_capture.rb +54 -0
- data/lib/girb/irb_integration.rb +87 -0
- data/lib/girb/prompt_builder.rb +173 -0
- data/lib/girb/providers/base.rb +65 -0
- data/lib/girb/session_history.rb +197 -0
- data/lib/girb/tools/base.rb +61 -0
- data/lib/girb/tools/evaluate_code.rb +43 -0
- data/lib/girb/tools/find_file.rb +93 -0
- data/lib/girb/tools/get_source.rb +145 -0
- data/lib/girb/tools/inspect_object.rb +54 -0
- data/lib/girb/tools/list_methods.rb +66 -0
- data/lib/girb/tools/rails_tools.rb +141 -0
- data/lib/girb/tools/read_file.rb +124 -0
- data/lib/girb/tools/session_history_tool.rb +183 -0
- data/lib/girb/tools.rb +38 -0
- data/lib/girb/version.rb +5 -0
- data/lib/girb.rb +53 -0
- data/lib/irb/command/qq.rb +55 -0
- metadata +131 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../session_history"
|
|
5
|
+
|
|
6
|
+
module Girb
|
|
7
|
+
module Tools
|
|
8
|
+
class SessionHistoryTool < Base
|
|
9
|
+
class << self
|
|
10
|
+
def name
|
|
11
|
+
"get_session_history"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def description
|
|
15
|
+
"Get IRB session history. Can retrieve specific lines, line ranges, method definitions, AI conversation details, or full history."
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def parameters
|
|
19
|
+
{
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
action: {
|
|
23
|
+
type: "string",
|
|
24
|
+
enum: %w[get_line get_range get_method list_methods full_history list_ai_conversations get_ai_detail],
|
|
25
|
+
description: "Action to perform: get_line (single line), get_range (line range), get_method (method source), list_methods (list defined methods), full_history (all history), list_ai_conversations (list AI Q&A), get_ai_detail (get AI response with reasoning)"
|
|
26
|
+
},
|
|
27
|
+
line: {
|
|
28
|
+
type: "integer",
|
|
29
|
+
description: "Line number for get_line action"
|
|
30
|
+
},
|
|
31
|
+
start_line: {
|
|
32
|
+
type: "integer",
|
|
33
|
+
description: "Start line for get_range action"
|
|
34
|
+
},
|
|
35
|
+
end_line: {
|
|
36
|
+
type: "integer",
|
|
37
|
+
description: "End line for get_range action"
|
|
38
|
+
},
|
|
39
|
+
method_name: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Method name for get_method action"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
required: ["action"]
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def execute(_binding, action:, line: nil, start_line: nil, end_line: nil, method_name: nil)
|
|
50
|
+
case action
|
|
51
|
+
when "get_line"
|
|
52
|
+
get_single_line(line)
|
|
53
|
+
when "get_range"
|
|
54
|
+
get_line_range(start_line, end_line)
|
|
55
|
+
when "get_method"
|
|
56
|
+
get_method_source(method_name)
|
|
57
|
+
when "list_methods"
|
|
58
|
+
list_defined_methods
|
|
59
|
+
when "full_history"
|
|
60
|
+
get_full_history
|
|
61
|
+
when "list_ai_conversations"
|
|
62
|
+
list_ai_conversations
|
|
63
|
+
when "get_ai_detail"
|
|
64
|
+
get_ai_detail(line)
|
|
65
|
+
else
|
|
66
|
+
{ error: "Unknown action: #{action}" }
|
|
67
|
+
end
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
{ error: "#{e.class}: #{e.message}" }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def get_single_line(line)
|
|
75
|
+
return { error: "line parameter is required" } unless line
|
|
76
|
+
|
|
77
|
+
entry = SessionHistory.find_by_line(line)
|
|
78
|
+
if entry
|
|
79
|
+
{
|
|
80
|
+
line: entry.line_no,
|
|
81
|
+
code: entry.code,
|
|
82
|
+
method_definition: entry.method_definition&.name
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
{ error: "Line #{line} not found in session history" }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def get_line_range(start_line, end_line)
|
|
90
|
+
return { error: "start_line and end_line parameters are required" } unless start_line && end_line
|
|
91
|
+
|
|
92
|
+
entries = SessionHistory.find_by_line_range(start_line, end_line)
|
|
93
|
+
if entries.any?
|
|
94
|
+
{
|
|
95
|
+
range: "#{start_line}-#{end_line}",
|
|
96
|
+
entries: entries.map { |e| { line: e.line_no, code: e.code } }
|
|
97
|
+
}
|
|
98
|
+
else
|
|
99
|
+
{ error: "No entries found in range #{start_line}-#{end_line}" }
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def get_method_source(method_name)
|
|
104
|
+
return { error: "method_name parameter is required" } unless method_name
|
|
105
|
+
|
|
106
|
+
method_def = SessionHistory.find_method(method_name)
|
|
107
|
+
if method_def
|
|
108
|
+
{
|
|
109
|
+
method_name: method_def.name,
|
|
110
|
+
start_line: method_def.start_line,
|
|
111
|
+
end_line: method_def.end_line,
|
|
112
|
+
source: method_def.code
|
|
113
|
+
}
|
|
114
|
+
else
|
|
115
|
+
{ error: "Method '#{method_name}' not found in session history" }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def list_defined_methods
|
|
120
|
+
methods = SessionHistory.method_definitions
|
|
121
|
+
if methods.any?
|
|
122
|
+
{
|
|
123
|
+
count: methods.size,
|
|
124
|
+
methods: methods.map do |m|
|
|
125
|
+
{
|
|
126
|
+
name: m.name,
|
|
127
|
+
lines: "#{m.start_line}-#{m.end_line}"
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
}
|
|
131
|
+
else
|
|
132
|
+
{ message: "No methods defined in this session" }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def get_full_history
|
|
137
|
+
history = SessionHistory.all_with_line_numbers
|
|
138
|
+
if history.any?
|
|
139
|
+
{
|
|
140
|
+
count: history.size,
|
|
141
|
+
history: history
|
|
142
|
+
}
|
|
143
|
+
else
|
|
144
|
+
{ message: "No history in this session" }
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def list_ai_conversations
|
|
149
|
+
conversations = SessionHistory.ai_conversations
|
|
150
|
+
if conversations.any?
|
|
151
|
+
{
|
|
152
|
+
count: conversations.size,
|
|
153
|
+
conversations: conversations.map do |c|
|
|
154
|
+
{
|
|
155
|
+
line: c[:line_no],
|
|
156
|
+
question: c[:question],
|
|
157
|
+
response_preview: c[:response][0, 200] + (c[:response].length > 200 ? "..." : "")
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
}
|
|
161
|
+
else
|
|
162
|
+
{ message: "No AI conversations in this session" }
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def get_ai_detail(line)
|
|
167
|
+
return { error: "line parameter is required" } unless line
|
|
168
|
+
|
|
169
|
+
detail = SessionHistory.get_ai_detail(line)
|
|
170
|
+
if detail
|
|
171
|
+
{
|
|
172
|
+
line: detail[:line_no],
|
|
173
|
+
question: detail[:question],
|
|
174
|
+
response: detail[:response],
|
|
175
|
+
reasoning: detail[:reasoning]
|
|
176
|
+
}
|
|
177
|
+
else
|
|
178
|
+
{ error: "No AI conversation found at line #{line}" }
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
data/lib/girb/tools.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tools/base"
|
|
4
|
+
require_relative "tools/inspect_object"
|
|
5
|
+
require_relative "tools/get_source"
|
|
6
|
+
require_relative "tools/list_methods"
|
|
7
|
+
require_relative "tools/evaluate_code"
|
|
8
|
+
require_relative "tools/read_file"
|
|
9
|
+
require_relative "tools/find_file"
|
|
10
|
+
require_relative "tools/session_history_tool"
|
|
11
|
+
|
|
12
|
+
module Girb
|
|
13
|
+
module Tools
|
|
14
|
+
CORE_TOOLS = [InspectObject, GetSource, ListMethods, EvaluateCode, ReadFile, FindFile, SessionHistoryTool].freeze
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def available_tools
|
|
18
|
+
tools = CORE_TOOLS.dup
|
|
19
|
+
|
|
20
|
+
# Rails tools are loaded conditionally
|
|
21
|
+
if defined?(ActiveRecord::Base)
|
|
22
|
+
require_relative "tools/rails_tools"
|
|
23
|
+
tools << RailsModelInfo
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
tools.select { |t| !t.respond_to?(:available?) || t.available? }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_tool(name)
|
|
30
|
+
available_tools.find { |t| t.tool_name == name }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_gemini_tools
|
|
34
|
+
available_tools.map(&:to_gemini_tool)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/girb/version.rb
ADDED
data/lib/girb.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "girb/version"
|
|
4
|
+
require_relative "girb/configuration"
|
|
5
|
+
require_relative "girb/providers/base"
|
|
6
|
+
require_relative "girb/exception_capture"
|
|
7
|
+
require_relative "girb/context_builder"
|
|
8
|
+
require_relative "girb/prompt_builder"
|
|
9
|
+
require_relative "girb/conversation_history"
|
|
10
|
+
require_relative "girb/tools"
|
|
11
|
+
require_relative "girb/ai_client"
|
|
12
|
+
|
|
13
|
+
module Girb
|
|
14
|
+
class Error < StandardError; end
|
|
15
|
+
class ConfigurationError < Error; end
|
|
16
|
+
class ApiError < Error; end
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
attr_accessor :configuration
|
|
20
|
+
|
|
21
|
+
def configure
|
|
22
|
+
self.configuration ||= Configuration.new
|
|
23
|
+
yield(configuration) if block_given?
|
|
24
|
+
configuration
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def setup!
|
|
28
|
+
configure unless configuration
|
|
29
|
+
require_relative "girb/irb_integration"
|
|
30
|
+
IrbIntegration.setup
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# IRB がロードされていたら自動で組み込む
|
|
36
|
+
if defined?(IRB)
|
|
37
|
+
Girb.setup!
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# binding.girb サポート
|
|
41
|
+
class Binding
|
|
42
|
+
def girb
|
|
43
|
+
require "irb"
|
|
44
|
+
Girb.setup! unless defined?(IRB::Command::Qq)
|
|
45
|
+
|
|
46
|
+
# IRB.start with this binding
|
|
47
|
+
IRB.setup(source_location[0], argv: [])
|
|
48
|
+
workspace = IRB::WorkSpace.new(self)
|
|
49
|
+
irb = IRB::Irb.new(workspace)
|
|
50
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
|
51
|
+
irb.run(IRB.conf)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "irb/command"
|
|
4
|
+
|
|
5
|
+
module IRB
|
|
6
|
+
module Command
|
|
7
|
+
class Qq < Base
|
|
8
|
+
category "AI Assistant"
|
|
9
|
+
description "Ask AI a question about your code"
|
|
10
|
+
|
|
11
|
+
def execute(question)
|
|
12
|
+
question = question.to_s.strip
|
|
13
|
+
if question.empty?
|
|
14
|
+
puts "[girb] Usage: qq \"your question here\""
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
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' }"
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
current_binding = irb_context.workspace.binding
|
|
27
|
+
|
|
28
|
+
# AI質問を履歴に記録
|
|
29
|
+
line_no = irb_context.line_no rescue 0
|
|
30
|
+
Girb::SessionHistory.record(line_no, question, is_ai_question: true)
|
|
31
|
+
|
|
32
|
+
context = Girb::ContextBuilder.new(
|
|
33
|
+
current_binding,
|
|
34
|
+
irb_context
|
|
35
|
+
).build
|
|
36
|
+
|
|
37
|
+
if Girb.configuration.debug
|
|
38
|
+
puts "[girb] Context collected:"
|
|
39
|
+
require "yaml"
|
|
40
|
+
puts context.to_yaml
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
client = Girb::AiClient.new
|
|
44
|
+
client.ask(question, context, binding: current_binding, line_no: line_no)
|
|
45
|
+
rescue Girb::Error => e
|
|
46
|
+
puts "[girb] Error: #{e.message}"
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
puts "[girb] Error: #{e.message}"
|
|
49
|
+
puts e.backtrace.first(5).join("\n") if Girb.configuration&.debug
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
IRB::Command.register(:qq, IRB::Command::Qq)
|
metadata
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: girb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- rira100000000
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: irb
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 1.6.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 1.6.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rspec
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: webmock
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.0'
|
|
68
|
+
description: Ask questions in IRB and get AI-powered answers based on your runtime
|
|
69
|
+
context. Access local variables, exception info, and Rails model data while debugging.
|
|
70
|
+
Requires an LLM provider gem (e.g., girb-ruby_llm) or custom provider implementation.
|
|
71
|
+
email:
|
|
72
|
+
- 101010hayakawa@gmail.com
|
|
73
|
+
executables:
|
|
74
|
+
- girb
|
|
75
|
+
extensions: []
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
files:
|
|
78
|
+
- ".rspec"
|
|
79
|
+
- CHANGELOG.md
|
|
80
|
+
- LICENSE
|
|
81
|
+
- README.md
|
|
82
|
+
- README_ja.md
|
|
83
|
+
- Rakefile
|
|
84
|
+
- exe/girb
|
|
85
|
+
- lib/girb.rb
|
|
86
|
+
- lib/girb/ai_client.rb
|
|
87
|
+
- lib/girb/configuration.rb
|
|
88
|
+
- lib/girb/context_builder.rb
|
|
89
|
+
- lib/girb/conversation_history.rb
|
|
90
|
+
- lib/girb/exception_capture.rb
|
|
91
|
+
- lib/girb/irb_integration.rb
|
|
92
|
+
- lib/girb/prompt_builder.rb
|
|
93
|
+
- lib/girb/providers/base.rb
|
|
94
|
+
- lib/girb/session_history.rb
|
|
95
|
+
- lib/girb/tools.rb
|
|
96
|
+
- lib/girb/tools/base.rb
|
|
97
|
+
- lib/girb/tools/evaluate_code.rb
|
|
98
|
+
- lib/girb/tools/find_file.rb
|
|
99
|
+
- lib/girb/tools/get_source.rb
|
|
100
|
+
- lib/girb/tools/inspect_object.rb
|
|
101
|
+
- lib/girb/tools/list_methods.rb
|
|
102
|
+
- lib/girb/tools/rails_tools.rb
|
|
103
|
+
- lib/girb/tools/read_file.rb
|
|
104
|
+
- lib/girb/tools/session_history_tool.rb
|
|
105
|
+
- lib/girb/version.rb
|
|
106
|
+
- lib/irb/command/qq.rb
|
|
107
|
+
homepage: https://github.com/rira100000000/girb
|
|
108
|
+
licenses:
|
|
109
|
+
- MIT
|
|
110
|
+
metadata:
|
|
111
|
+
homepage_uri: https://github.com/rira100000000/girb
|
|
112
|
+
source_code_uri: https://github.com/rira100000000/girb
|
|
113
|
+
changelog_uri: https://github.com/rira100000000/girb/blob/main/CHANGELOG.md
|
|
114
|
+
rdoc_options: []
|
|
115
|
+
require_paths:
|
|
116
|
+
- lib
|
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - ">="
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: 3.2.0
|
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: '0'
|
|
127
|
+
requirements: []
|
|
128
|
+
rubygems_version: 3.7.2
|
|
129
|
+
specification_version: 4
|
|
130
|
+
summary: AI-powered IRB assistant
|
|
131
|
+
test_files: []
|