ollama_chat 0.0.113 → 0.0.115
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/CHANGES.md +120 -0
- data/README.md +7 -0
- data/bin/ollama_chat_log +1 -1
- data/bin/ollama_chat_send +3 -1
- data/lib/ollama_chat/chat.rb +30 -14
- data/lib/ollama_chat/commands.rb +36 -18
- data/lib/ollama_chat/compaction.rb +289 -0
- data/lib/ollama_chat/config_handling.rb +32 -0
- data/lib/ollama_chat/database/migrations/010_add_enabled_to_collections.rb +7 -0
- data/lib/ollama_chat/database/models/app_state.rb +48 -16
- data/lib/ollama_chat/database/models/session.rb +1 -2
- data/lib/ollama_chat/database.rb +4 -2
- data/lib/ollama_chat/information.rb +53 -6
- data/lib/ollama_chat/logging.rb +3 -2
- data/lib/ollama_chat/message.rb +22 -1
- data/lib/ollama_chat/message_format.rb +1 -2
- data/lib/ollama_chat/message_list.rb +223 -9
- data/lib/ollama_chat/model_handling.rb +1 -1
- data/lib/ollama_chat/oc.rb +17 -7
- data/lib/ollama_chat/ollama_chat_config/default_config.yml +72 -3
- data/lib/ollama_chat/parsing.rb +2 -1
- data/lib/ollama_chat/prompt_handling.rb +1 -1
- data/lib/ollama_chat/prompt_management.rb +1 -0
- data/lib/ollama_chat/rag_handling.rb +19 -5
- data/lib/ollama_chat/session_management.rb +5 -3
- data/lib/ollama_chat/token_estimator/crude.rb +1 -1
- data/lib/ollama_chat/token_estimator.rb +1 -1
- data/lib/ollama_chat/tools/concern.rb +17 -0
- data/lib/ollama_chat/tools/directory_structure.rb +11 -7
- data/lib/ollama_chat/tools/eval_ruby.rb +16 -6
- data/lib/ollama_chat/tools/execute_shell.rb +158 -0
- data/lib/ollama_chat/tools/get_rfc.rb +2 -0
- data/lib/ollama_chat/tools/lookup_group.rb +100 -0
- data/lib/ollama_chat/tools/paste_from_clipboard.rb +1 -1
- data/lib/ollama_chat/tools/patch_file.rb +23 -5
- data/lib/ollama_chat/tools/read_file.rb +1 -1
- data/lib/ollama_chat/tools/resolve_tag.rb +1 -1
- data/lib/ollama_chat/tools/search_knowledge.rb +7 -1
- data/lib/ollama_chat/tools/write_file.rb +25 -6
- data/lib/ollama_chat/tools.rb +2 -0
- data/lib/ollama_chat/utils/analyze_directory.rb +5 -4
- data/lib/ollama_chat/utils/fetcher.rb +1 -1
- data/lib/ollama_chat/utils/log_rotation.rb +29 -0
- data/lib/ollama_chat/utils/strip_ansi.rb +22 -0
- data/lib/ollama_chat/utils/tag_resolver.rb +3 -3
- data/lib/ollama_chat/utils.rb +2 -0
- data/lib/ollama_chat/version.rb +1 -1
- data/lib/ollama_chat.rb +7 -0
- data/ollama_chat.gemspec +5 -5
- data/spec/ollama_chat/commands_spec.rb +25 -0
- data/spec/ollama_chat/compaction_spec.rb +381 -0
- data/spec/ollama_chat/config_handling_spec.rb +60 -0
- data/spec/ollama_chat/message_list_spec.rb +229 -0
- data/spec/ollama_chat/message_spec.rb +143 -0
- data/spec/ollama_chat/rag_handling_spec.rb +3 -0
- data/spec/ollama_chat/system_prompt_management_spec.rb +0 -2
- data/spec/ollama_chat/tools/browse_spec.rb +7 -0
- data/spec/ollama_chat/tools/compute_bmi_spec.rb +15 -0
- data/spec/ollama_chat/tools/concern_spec.rb +77 -0
- data/spec/ollama_chat/tools/copy_to_clipboard_spec.rb +8 -0
- data/spec/ollama_chat/tools/delete_file_spec.rb +8 -0
- data/spec/ollama_chat/tools/directory_structure_spec.rb +4 -0
- data/spec/ollama_chat/tools/execute_grep_spec.rb +13 -0
- data/spec/ollama_chat/tools/execute_ri_spec.rb +8 -0
- data/spec/ollama_chat/tools/execute_shell_spec.rb +232 -0
- data/spec/ollama_chat/tools/file_context_spec.rb +5 -0
- data/spec/ollama_chat/tools/gem_path_lookup_spec.rb +2 -0
- data/spec/ollama_chat/tools/generate_image_spec.rb +12 -0
- data/spec/ollama_chat/tools/generate_password_spec.rb +5 -0
- data/spec/ollama_chat/tools/get_cve_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_endoflife_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_ghr_spec.rb +6 -0
- data/spec/ollama_chat/tools/get_location_spec.rb +4 -0
- data/spec/ollama_chat/tools/get_rfc_spec.rb +2 -0
- data/spec/ollama_chat/tools/get_time_spec.rb +2 -0
- data/spec/ollama_chat/tools/get_url_spec.rb +7 -0
- data/spec/ollama_chat/tools/lookup_group_spec.rb +111 -0
- data/spec/ollama_chat/tools/move_file_spec.rb +10 -0
- data/spec/ollama_chat/tools/open_file_in_editor_spec.rb +6 -0
- data/spec/ollama_chat/tools/paste_from_clipboard_spec.rb +5 -0
- data/spec/ollama_chat/tools/paste_into_editor_spec.rb +6 -0
- data/spec/ollama_chat/tools/patch_file_spec.rb +62 -0
- data/spec/ollama_chat/tools/read_file_spec.rb +10 -3
- data/spec/ollama_chat/tools/resolve_tag_spec.rb +46 -0
- data/spec/ollama_chat/tools/roll_dice_spec.rb +11 -0
- data/spec/ollama_chat/tools/run_tests_spec.rb +8 -0
- data/spec/ollama_chat/tools/search_knowledge_spec.rb +2 -0
- data/spec/ollama_chat/tools/write_file_spec.rb +98 -2
- data/spec/ollama_chat/utils/analyze_directory_spec.rb +46 -7
- data/spec/ollama_chat/utils/log_rotation_spec.rb +71 -0
- data/spec/spec_helper.rb +2 -1
- metadata +25 -1
|
@@ -49,6 +49,8 @@ describe OllamaChat::Tools::MoveFile do
|
|
|
49
49
|
expect(File.exist?(source_path)).to be false
|
|
50
50
|
expect(File.exist?(dest_path)).to be true
|
|
51
51
|
expect(File.read(dest_path)).to eq content
|
|
52
|
+
expect(described_class.summary_template(result:))\
|
|
53
|
+
.to match(%r{File moved successfully from .*test_move_source.* to .*test_move_dest.*})
|
|
52
54
|
ensure
|
|
53
55
|
File.delete(source_path) if File.exist?(source_path)
|
|
54
56
|
File.delete(dest_path) if File.exist?(dest_path)
|
|
@@ -80,6 +82,8 @@ describe OllamaChat::Tools::MoveFile do
|
|
|
80
82
|
expect(json.success).to be_falsey
|
|
81
83
|
expect(json.error).to eq 'OllamaChat::InvalidPathError'
|
|
82
84
|
expect(json.message).to include('does already exist')
|
|
85
|
+
expect(described_class.summary_template(result:))\
|
|
86
|
+
.to include('does already exist')
|
|
83
87
|
|
|
84
88
|
# Verify files are still there (no move happened)
|
|
85
89
|
expect(File.exist?(source_path)).to be true
|
|
@@ -112,6 +116,8 @@ describe OllamaChat::Tools::MoveFile do
|
|
|
112
116
|
expect(json.success).to be_falsey
|
|
113
117
|
expect(json.error).to eq 'OllamaChat::InvalidPathError'
|
|
114
118
|
expect(json.message).to include('does not exist')
|
|
119
|
+
expect(described_class.summary_template(result:))\
|
|
120
|
+
.to include('does not exist')
|
|
115
121
|
ensure
|
|
116
122
|
File.delete(dest_path) if File.exist?(dest_path)
|
|
117
123
|
end
|
|
@@ -134,6 +140,8 @@ describe OllamaChat::Tools::MoveFile do
|
|
|
134
140
|
json = json_object(result)
|
|
135
141
|
expect(json.error).to eq 'OllamaChat::InvalidPathError'
|
|
136
142
|
expect(json.message).to include('is not within allowed directories')
|
|
143
|
+
expect(described_class.summary_template(result:))\
|
|
144
|
+
.to include('is not within allowed directories')
|
|
137
145
|
end
|
|
138
146
|
|
|
139
147
|
it 'handles general exceptions gracefully' do
|
|
@@ -161,6 +169,8 @@ describe OllamaChat::Tools::MoveFile do
|
|
|
161
169
|
json = json_object(result)
|
|
162
170
|
expect(json.error).to eq 'RuntimeError'
|
|
163
171
|
expect(json.message).to include('Unexpected system error')
|
|
172
|
+
expect(described_class.summary_template(result:))\
|
|
173
|
+
.to include('Unexpected system error')
|
|
164
174
|
ensure
|
|
165
175
|
File.delete(source_path) if File.exist?(source_path)
|
|
166
176
|
File.delete(dest_path) if File.exist?(dest_path)
|
|
@@ -32,6 +32,8 @@ describe OllamaChat::Tools::OpenFileInEditor do
|
|
|
32
32
|
expect(json.path).to eq asset('example.rb')
|
|
33
33
|
expect(json.start_line).to eq 42
|
|
34
34
|
expect(json.message).to match(%r(spec/assets/example.rb" at line 42))
|
|
35
|
+
expect(described_class.summary_template(result:)).
|
|
36
|
+
to match(%r(spec/assets/example.rb" at line 42))
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
it 'can be executed successfully with valid path, start_line, end_line' do
|
|
@@ -54,6 +56,8 @@ describe OllamaChat::Tools::OpenFileInEditor do
|
|
|
54
56
|
expect(json.start_line).to eq 23
|
|
55
57
|
expect(json.end_line).to eq 42
|
|
56
58
|
expect(json.message).to match(%r(spec/assets/example.rb" and selected range 23-42))
|
|
59
|
+
expect(described_class.summary_template(result:)).
|
|
60
|
+
to match(%r(spec/assets/example.rb" and selected range 23-42))
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
it 'can handle invalid path gracefully' do
|
|
@@ -74,6 +78,8 @@ describe OllamaChat::Tools::OpenFileInEditor do
|
|
|
74
78
|
|
|
75
79
|
expect(json.error).to eq 'Errno::ENOENT'
|
|
76
80
|
expect(json.message).to include('No such file or directory')
|
|
81
|
+
expect(described_class.summary_template(result:)).
|
|
82
|
+
to include('No such file or directory')
|
|
77
83
|
end
|
|
78
84
|
|
|
79
85
|
it 'can be converted to hash' do
|
|
@@ -40,6 +40,7 @@ describe OllamaChat::Tools::PasteFromClipboard do
|
|
|
40
40
|
expect(json.error).to be_nil # No exception was raised
|
|
41
41
|
expect(json.success).to be true
|
|
42
42
|
expect(json.message).to eq "Pasted 11.0 B of content."
|
|
43
|
+
expect(described_class.summary_template(result:)).to eq "Pasted 11.0 B of content."
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
it 'can be executed with edit option' do
|
|
@@ -65,6 +66,7 @@ describe OllamaChat::Tools::PasteFromClipboard do
|
|
|
65
66
|
expect(json.error).to be_nil
|
|
66
67
|
expect(json.success).to be true
|
|
67
68
|
expect(json.message).to eq "Pasted 18.0 B of content."
|
|
69
|
+
expect(described_class.summary_template(result:)).to eq "Pasted 18.0 B of content."
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
it 'can handle execution errors gracefully' do
|
|
@@ -89,6 +91,8 @@ describe OllamaChat::Tools::PasteFromClipboard do
|
|
|
89
91
|
json = json_object(result)
|
|
90
92
|
expect(json.error).to eq 'OllamaChat::OllamaChatError'
|
|
91
93
|
expect(json.message).to eq 'No content available to paste from the system clipboard.'
|
|
94
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
95
|
+
'No content available to paste from the system clipboard.'
|
|
92
96
|
end
|
|
93
97
|
|
|
94
98
|
it 'can handle execution exceptions gracefully' do
|
|
@@ -113,5 +117,6 @@ describe OllamaChat::Tools::PasteFromClipboard do
|
|
|
113
117
|
json = json_object(result)
|
|
114
118
|
expect(json.error).to eq 'RuntimeError'
|
|
115
119
|
expect(json.message).to eq 'some kind of exception'
|
|
120
|
+
expect(described_class.summary_template(result:)).to eq 'some kind of exception'
|
|
116
121
|
end
|
|
117
122
|
end
|
|
@@ -41,6 +41,8 @@ describe OllamaChat::Tools::PasteIntoEditor do
|
|
|
41
41
|
expect(json.message).to eq(
|
|
42
42
|
'The provided text has been successfully pasted into the editor.'
|
|
43
43
|
)
|
|
44
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
45
|
+
'The provided text has been successfully pasted into the editor.'
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
@@ -64,6 +66,8 @@ describe OllamaChat::Tools::PasteIntoEditor do
|
|
|
64
66
|
|
|
65
67
|
expect(json.error).to eq('OllamaChat::OllamaChatError')
|
|
66
68
|
expect(json.message).to eq('Insert failed')
|
|
69
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
70
|
+
'Insert failed'
|
|
67
71
|
end
|
|
68
72
|
|
|
69
73
|
it 'captures generic RuntimeError and returns JSON with the error details' do
|
|
@@ -75,6 +79,8 @@ describe OllamaChat::Tools::PasteIntoEditor do
|
|
|
75
79
|
|
|
76
80
|
expect(json.error).to eq('RuntimeError')
|
|
77
81
|
expect(json.message).to eq('Some exception')
|
|
82
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
83
|
+
'Some exception'
|
|
78
84
|
end
|
|
79
85
|
end
|
|
80
86
|
|
|
@@ -142,6 +142,8 @@ describe OllamaChat::Tools::PatchFile do
|
|
|
142
142
|
|
|
143
143
|
result = tool.execute(tool_call, chat:)
|
|
144
144
|
expect(json_object(result).success).to eq true
|
|
145
|
+
expect(described_class.summary_template(result:))\
|
|
146
|
+
.to match(/Successfully applied patch to/)
|
|
145
147
|
ensure
|
|
146
148
|
File.delete(test_file) if File.exist?(test_file)
|
|
147
149
|
end
|
|
@@ -152,6 +154,8 @@ describe OllamaChat::Tools::PatchFile do
|
|
|
152
154
|
|
|
153
155
|
result = tool.execute(tool_call, chat:)
|
|
154
156
|
expect(json_object(result).error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
157
|
+
expect(described_class.summary_template(result:))\
|
|
158
|
+
.to match(/Failed to apply patch to file/)
|
|
155
159
|
end
|
|
156
160
|
|
|
157
161
|
it 'can handle execution errors gracefully when path is not expected' do
|
|
@@ -188,4 +192,62 @@ describe OllamaChat::Tools::PatchFile do
|
|
|
188
192
|
ensure
|
|
189
193
|
File.delete(test_file) if File.exist?(test_file)
|
|
190
194
|
end
|
|
195
|
+
|
|
196
|
+
describe 'syntax check integration' do
|
|
197
|
+
let :test_rb_file do
|
|
198
|
+
"./tmp/patch_syntax_#{Tins::Token.new(bits: 128)}.rb"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
after do
|
|
202
|
+
File.delete(test_rb_file) if File.exist?(test_rb_file)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it 'includes syntax_check fail on broken .rb patch' do
|
|
206
|
+
const_conf_as('OC::DIFF_TOOL' => Pathname.new(`which true`.chomp))
|
|
207
|
+
File.write(test_rb_file, "def foo\n 42\nend\n")
|
|
208
|
+
content = File.read(test_rb_file)
|
|
209
|
+
|
|
210
|
+
edits = [{ start_line: 2, end_line: 2, text: 'end' }]
|
|
211
|
+
checksum = '%08x' % Zlib.crc32(content)
|
|
212
|
+
args_double = double('Arguments', path: test_rb_file, edits:, checksum:)
|
|
213
|
+
tool_call = double('ToolCall', function: double(name: 'patch_file', arguments: args_double))
|
|
214
|
+
|
|
215
|
+
tmp_double = double('Tempfile', write: true, flush: true, path: '/tmp/test_patch')
|
|
216
|
+
expect(chat).to receive(:edit_text_block).and_yield(tmp_double)
|
|
217
|
+
expect(tool).to receive(:system).and_return(true)
|
|
218
|
+
expect(tool).to receive(:digest).and_return 'old', 'new'
|
|
219
|
+
|
|
220
|
+
checker = double('checker', cmd: ['ruby', '-wc'])
|
|
221
|
+
expect(chat).to receive(:syntax_checker_for).and_return(checker)
|
|
222
|
+
expect(chat).to receive(:run_syntax_check).with(checker, any_args)
|
|
223
|
+
.and_return({ status: 'fail',
|
|
224
|
+
output: "test.rb:2: syntax error" })
|
|
225
|
+
|
|
226
|
+
result = tool.execute(tool_call, chat:)
|
|
227
|
+
json = json_object(result)
|
|
228
|
+
expect(json.message).to include('❌ Syntax error detected')
|
|
229
|
+
expect(json.syntax_check[:status]).to eq 'fail'
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'omits syntax_check when no checker matches' do
|
|
233
|
+
const_conf_as('OC::DIFF_TOOL' => Pathname.new(`which true`.chomp))
|
|
234
|
+
File.write(test_file, "Line 1\nLine 2\n")
|
|
235
|
+
content = File.read(test_file)
|
|
236
|
+
|
|
237
|
+
edits = [{ start_line: 2, end_line: 2, text: 'Modified' }]
|
|
238
|
+
checksum = '%08x' % Zlib.crc32(content)
|
|
239
|
+
args_double = double('Arguments', path: test_file, edits:, checksum:)
|
|
240
|
+
tool_call = double('ToolCall', function: double(name: 'patch_file', arguments: args_double))
|
|
241
|
+
|
|
242
|
+
tmp_double = double('Tempfile', write: true, flush: true, path: '/tmp/test_patch')
|
|
243
|
+
expect(chat).to receive(:edit_text_block).and_yield(tmp_double)
|
|
244
|
+
expect(tool).to receive(:system).and_return(true)
|
|
245
|
+
expect(tool).to receive(:digest).and_return 'old', 'new'
|
|
246
|
+
expect(chat).to receive(:syntax_checker_for).and_return(nil)
|
|
247
|
+
|
|
248
|
+
result = tool.execute(tool_call, chat:)
|
|
249
|
+
json = json_object(result)
|
|
250
|
+
expect(json.syntax_check).to be_nil
|
|
251
|
+
end
|
|
252
|
+
end
|
|
191
253
|
end
|
|
@@ -17,6 +17,7 @@ describe OllamaChat::Tools::ReadFile do
|
|
|
17
17
|
expect(described_class.new.to_hash).to be_a Hash
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
|
|
20
21
|
it 'can be executed successfully' do
|
|
21
22
|
tool_call = double(
|
|
22
23
|
'ToolCall',
|
|
@@ -39,10 +40,12 @@ describe OllamaChat::Tools::ReadFile do
|
|
|
39
40
|
expect(json.content).to eq <<~EOT
|
|
40
41
|
puts "Hello World!"
|
|
41
42
|
EOT
|
|
42
|
-
expect(json.message).to include('Read
|
|
43
|
+
expect(json.message).to include('Read 6.0 T (20.0 B) from')
|
|
43
44
|
expect(json.mtime).to match(/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
|
44
45
|
expect(json.line_count).to be_nil
|
|
45
46
|
expect(json.checksum).not_to be_present
|
|
47
|
+
expect(described_class.summary_template(result:))\
|
|
48
|
+
.to include('Read 6.0 T (20.0 B) from')
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
it 'can extract range when start_line is provided and end_line is nil' do
|
|
@@ -61,7 +64,7 @@ describe OllamaChat::Tools::ReadFile do
|
|
|
61
64
|
result = described_class.new.execute(tool_call, chat:)
|
|
62
65
|
json = json_object(result)
|
|
63
66
|
expect(json.content).to eq "puts \"Hello World!\"\n"
|
|
64
|
-
expect(json.message).to include('Read
|
|
67
|
+
expect(json.message).to include('Read 6.0 T (20.0 B) from')
|
|
65
68
|
expect(json.mtime).to match(/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
|
66
69
|
expect(json.line_count).to be_nil
|
|
67
70
|
expect(json.checksum).not_to be_present
|
|
@@ -83,7 +86,7 @@ describe OllamaChat::Tools::ReadFile do
|
|
|
83
86
|
result = described_class.new.execute(tool_call, chat:)
|
|
84
87
|
json = json_object(result)
|
|
85
88
|
expect(json.content).to eq "puts \"Hello World!\"\n"
|
|
86
|
-
expect(json.message).to include('Read
|
|
89
|
+
expect(json.message).to include('Read 6.0 T (20.0 B) from')
|
|
87
90
|
expect(json.mtime).to match(/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
|
|
88
91
|
expect(json.line_count).to be_nil
|
|
89
92
|
expect(json.checksum).not_to be_present
|
|
@@ -198,6 +201,8 @@ describe OllamaChat::Tools::ReadFile do
|
|
|
198
201
|
expect(json.path).to eq '/etc/passwd'
|
|
199
202
|
expect(json.message).to include('is not within allowed directories')
|
|
200
203
|
expect(json.checksum).not_to be_present
|
|
204
|
+
expect(described_class.summary_template(result:))\
|
|
205
|
+
.to include('is not within allowed directories')
|
|
201
206
|
end
|
|
202
207
|
|
|
203
208
|
it 'can handle exceptions gracefully' do
|
|
@@ -222,5 +227,7 @@ describe OllamaChat::Tools::ReadFile do
|
|
|
222
227
|
expect(json.error).to eq 'OllamaChat::InvalidPathError'
|
|
223
228
|
expect(json.checksum).to be_nil
|
|
224
229
|
expect(json.message).to match(/Failed to read file:.* does not exist/)
|
|
230
|
+
expect(described_class.summary_template(result:))\
|
|
231
|
+
.to match(/Failed to read file:.* does not exist/)
|
|
225
232
|
end
|
|
226
233
|
end
|
|
@@ -60,6 +60,8 @@ describe OllamaChat::Tools::ResolveTag do
|
|
|
60
60
|
expect(json.results).to be_present
|
|
61
61
|
expect(json.symbol).to eq 'execute'
|
|
62
62
|
expect(json.kind).to eq ?f
|
|
63
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
64
|
+
'Found 2 results of symbol "execute".'
|
|
63
65
|
end
|
|
64
66
|
end
|
|
65
67
|
|
|
@@ -85,6 +87,50 @@ describe OllamaChat::Tools::ResolveTag do
|
|
|
85
87
|
json = json_object(result)
|
|
86
88
|
expect(json.error).to eq('RuntimeError')
|
|
87
89
|
expect(json.message).to include('some error')
|
|
90
|
+
expect(described_class.summary_template(result:)).to include('some error')
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context 'when a tag line triggers the internal rescue path' do
|
|
95
|
+
let(:tags_path) { File.join(Dir.pwd, 'tmp', 'test_tags.ctags') }
|
|
96
|
+
|
|
97
|
+
before do
|
|
98
|
+
FileUtils.mkdir_p(File.dirname(tags_path))
|
|
99
|
+
# Valid ctags format: symbol\tfilename\t/^regexp$/;"\tkind rest
|
|
100
|
+
# 5 regex captures, but TagResult needs 6 (…+ :linenumber),
|
|
101
|
+
# so TagResult.new always raises ArgumentError → rescue fires.
|
|
102
|
+
File.write(tags_path, \
|
|
103
|
+
"execute\tlib/foo.rb\t/^ def execute\\($/;\"\tf method\n")
|
|
104
|
+
const_conf_as(
|
|
105
|
+
'OC::OLLAMA::CHAT::TOOLS::CTAGS_TOOL' => 'true',
|
|
106
|
+
'OC::OLLAMA::CHAT::TOOLS::TAGS_FILE' => Pathname.new(tags_path),
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
after do
|
|
111
|
+
File.delete(tags_path) if File.exist?(tags_path)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'logs via chat.log and returns empty results' do
|
|
115
|
+
tool_call = double(
|
|
116
|
+
'ToolCall',
|
|
117
|
+
function: double(
|
|
118
|
+
name: 'resolve_tag',
|
|
119
|
+
arguments: double(symbol: 'execute', kind: nil, directory: nil)
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
expect(chat).to receive(:log).
|
|
124
|
+
with(:error, kind_of(ArgumentError),
|
|
125
|
+
hash_including(data: { context: 'tag_resolver' })).
|
|
126
|
+
and_return(nil)
|
|
127
|
+
|
|
128
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
129
|
+
json = json_object(result)
|
|
130
|
+
|
|
131
|
+
expect(json.error).to be_nil
|
|
132
|
+
expect(json.results).to eq []
|
|
133
|
+
expect(json.message).to eq 'Found 0 results of symbol "execute".'
|
|
88
134
|
end
|
|
89
135
|
end
|
|
90
136
|
end
|
|
@@ -44,6 +44,8 @@ describe OllamaChat::Tools::RollDice do
|
|
|
44
44
|
expect(json.modifier).to eq 0
|
|
45
45
|
expect(json.total).to eq 7
|
|
46
46
|
expect(json.message).to eq 'Dice roll was: 2d6 = 2…12 = (2 + 5) = 7'
|
|
47
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
48
|
+
'Dice roll was: 2d6 = 2…12 = (2 + 5) = 7'
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
it 'can be executed successfully with d20 notation' do
|
|
@@ -65,6 +67,8 @@ describe OllamaChat::Tools::RollDice do
|
|
|
65
67
|
expect(json.rolls).to eq [10]
|
|
66
68
|
expect(json.total).to eq 10
|
|
67
69
|
expect(json.message).to eq 'Dice roll was: d20 = 1…20 = (10) = 10'
|
|
70
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
71
|
+
'Dice roll was: d20 = 1…20 = (10) = 10'
|
|
68
72
|
end
|
|
69
73
|
|
|
70
74
|
it 'can be executed successfully with positive modifier' do
|
|
@@ -87,6 +91,8 @@ describe OllamaChat::Tools::RollDice do
|
|
|
87
91
|
expect(json.modifier).to eq 3
|
|
88
92
|
expect(json.total).to eq 18
|
|
89
93
|
expect(json.message).to eq 'Dice roll was: d20+3 = 4…23 = (15) + 3 = 18'
|
|
94
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
95
|
+
'Dice roll was: d20+3 = 4…23 = (15) + 3 = 18'
|
|
90
96
|
end
|
|
91
97
|
|
|
92
98
|
it 'can be executed successfully with negative modifier' do
|
|
@@ -109,6 +115,8 @@ describe OllamaChat::Tools::RollDice do
|
|
|
109
115
|
expect(json.modifier).to eq(-3)
|
|
110
116
|
expect(json.total).to eq 2
|
|
111
117
|
expect(json.message).to eq 'Dice roll was: d20-3 = -2…17 = (5) - 3 = 2'
|
|
118
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
119
|
+
'Dice roll was: d20-3 = -2…17 = (5) - 3 = 2'
|
|
112
120
|
end
|
|
113
121
|
|
|
114
122
|
it 'can handle invalid dice notation' do
|
|
@@ -126,6 +134,9 @@ describe OllamaChat::Tools::RollDice do
|
|
|
126
134
|
json = json_object(result)
|
|
127
135
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
128
136
|
expect(json.message).to match(/Invalid dice notation/)
|
|
137
|
+
expect(described_class.summary_template(result:)).to match(
|
|
138
|
+
/Invalid dice notation/
|
|
139
|
+
)
|
|
129
140
|
end
|
|
130
141
|
|
|
131
142
|
it 'can be converted to hash' do
|
|
@@ -60,6 +60,9 @@ describe OllamaChat::Tools::RunTests do
|
|
|
60
60
|
expect(json.success).to be true
|
|
61
61
|
expect(json.path).to eq 'spec/ollama_chat/tools/my_spec.rb'
|
|
62
62
|
expect(json.status).to eq 'passed'
|
|
63
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
64
|
+
'✨ All tests passed successfully in ' \
|
|
65
|
+
'"spec/ollama_chat/tools/my_spec.rb"!'
|
|
63
66
|
end
|
|
64
67
|
|
|
65
68
|
it 'can be executed successfully with a path and coverage' do
|
|
@@ -108,6 +111,10 @@ describe OllamaChat::Tools::RunTests do
|
|
|
108
111
|
json = json_object(result)
|
|
109
112
|
expect(json.success).to be false
|
|
110
113
|
expect(json.status).to eq 'failed'
|
|
114
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
115
|
+
'❌ Some tests failed in ' \
|
|
116
|
+
'"spec/ollama_chat/tools/my_spec.rb". ' \
|
|
117
|
+
'Please check the error messages above.'
|
|
111
118
|
end
|
|
112
119
|
|
|
113
120
|
it 'can handle unexpected errors gracefully' do
|
|
@@ -129,6 +136,7 @@ describe OllamaChat::Tools::RunTests do
|
|
|
129
136
|
json = json_object(result)
|
|
130
137
|
expect(json.error).to eq 'StandardError'
|
|
131
138
|
expect(json.message).to eq 'Unexpected boom'
|
|
139
|
+
expect(described_class.summary_template(result:)).to eq 'Unexpected boom'
|
|
132
140
|
end
|
|
133
141
|
|
|
134
142
|
context 'path auto-discovery' do
|
|
@@ -120,6 +120,8 @@ describe OllamaChat::Tools::SearchKnowledge do
|
|
|
120
120
|
|
|
121
121
|
mock_docs = double('Documents')
|
|
122
122
|
expect(chat).to receive(:documents).and_return(mock_docs).at_least(:once)
|
|
123
|
+
expect(chat).to receive(:database_collection?).with('tolkien').
|
|
124
|
+
and_return(double('Col', enabled: true))
|
|
123
125
|
|
|
124
126
|
expect(mock_docs).to receive(:collection).and_return('default_collection').
|
|
125
127
|
at_least(:once)
|
|
@@ -41,7 +41,9 @@ describe OllamaChat::Tools::WriteFile do
|
|
|
41
41
|
json = json_object(result)
|
|
42
42
|
expect(json.success).to eq true
|
|
43
43
|
expect(json.path).to include(File.basename(test_write_file))
|
|
44
|
-
expect(json.message).to include('Wrote
|
|
44
|
+
expect(json.message).to include('Wrote 4.0 T (13.0 B) to file')
|
|
45
|
+
expect(described_class.summary_template(result:)).
|
|
46
|
+
to include('Wrote 4.0 T (13.0 B) to file')
|
|
45
47
|
|
|
46
48
|
# Verify file was actually written
|
|
47
49
|
expect(File.exist?(test_write_file)).to be true
|
|
@@ -74,6 +76,8 @@ describe OllamaChat::Tools::WriteFile do
|
|
|
74
76
|
json = json_object(result)
|
|
75
77
|
expect(json.success).to eq true
|
|
76
78
|
expect(File.read(test_write_file)).to eq 'New content'
|
|
79
|
+
expect(described_class.summary_template(result:)).
|
|
80
|
+
to include('Wrote 4.0 T (11.0 B) to file')
|
|
77
81
|
ensure
|
|
78
82
|
File.delete(test_write_file) if File.exist?(test_write_file)
|
|
79
83
|
end
|
|
@@ -101,6 +105,8 @@ describe OllamaChat::Tools::WriteFile do
|
|
|
101
105
|
json = json_object(result)
|
|
102
106
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
103
107
|
expect(json.message).to include('Write rejected')
|
|
108
|
+
expect(described_class.summary_template(result:)).
|
|
109
|
+
to include('Write rejected')
|
|
104
110
|
expect(File.read(test_write_file)).to eq 'Old content'
|
|
105
111
|
ensure
|
|
106
112
|
File.delete(test_write_file) if File.exist?(test_write_file)
|
|
@@ -130,7 +136,9 @@ describe OllamaChat::Tools::WriteFile do
|
|
|
130
136
|
json = json_object(result)
|
|
131
137
|
expect(json.success).to be true
|
|
132
138
|
expect(json.path).to include(File.basename(test_write_file))
|
|
133
|
-
expect(json.message).to include('Wrote
|
|
139
|
+
expect(json.message).to include('Wrote 6.0 T (18.0 B) to file')
|
|
140
|
+
expect(described_class.summary_template(result:)).
|
|
141
|
+
to include('Wrote 6.0 T (18.0 B) to file')
|
|
134
142
|
|
|
135
143
|
# Verify file was actually appended
|
|
136
144
|
expect(File.exist?(test_write_file)).to be true
|
|
@@ -162,6 +170,8 @@ describe OllamaChat::Tools::WriteFile do
|
|
|
162
170
|
json = json_object(result)
|
|
163
171
|
expect(json.success).to eq true
|
|
164
172
|
expect(File.read(test_write_file)).to eq 'First content\n'
|
|
173
|
+
expect(described_class.summary_template(result:)).
|
|
174
|
+
to include('Wrote 5.0 T (15.0 B) to file')
|
|
165
175
|
ensure
|
|
166
176
|
File.delete(test_write_file) if File.exist?(test_write_file)
|
|
167
177
|
end
|
|
@@ -186,6 +196,8 @@ describe OllamaChat::Tools::WriteFile do
|
|
|
186
196
|
json = json_object(result)
|
|
187
197
|
expect(json.error).to eq 'OllamaChat::ToolFunctionArgumentError'
|
|
188
198
|
expect(json.message).to include('Write rejected')
|
|
199
|
+
expect(described_class.summary_template(result:)).
|
|
200
|
+
to include('Write rejected')
|
|
189
201
|
expect(File.exist?(test_write_file)).to be false
|
|
190
202
|
end
|
|
191
203
|
|
|
@@ -210,6 +222,8 @@ describe OllamaChat::Tools::WriteFile do
|
|
|
210
222
|
expect(json.error).to eq 'OllamaChat::InvalidPathError'
|
|
211
223
|
expect(json.path).to eq '/etc/passwd'
|
|
212
224
|
expect(json.message).to include('is not within allowed directories')
|
|
225
|
+
expect(described_class.summary_template(result:)).
|
|
226
|
+
to include('is not within allowed directories')
|
|
213
227
|
end
|
|
214
228
|
|
|
215
229
|
it 'can handle execution errors gracefully when mode is invalid' do
|
|
@@ -232,6 +246,8 @@ describe OllamaChat::Tools::WriteFile do
|
|
|
232
246
|
json = json_object(result)
|
|
233
247
|
expect(json.error).to eq 'ArgumentError'
|
|
234
248
|
expect(json.message).to include('Invalid mode')
|
|
249
|
+
expect(described_class.summary_template(result:)).
|
|
250
|
+
to include('Invalid mode')
|
|
235
251
|
end
|
|
236
252
|
|
|
237
253
|
it 'can handle exceptions gracefully' do
|
|
@@ -256,5 +272,85 @@ describe OllamaChat::Tools::WriteFile do
|
|
|
256
272
|
expect(json.error).to eq 'RuntimeError'
|
|
257
273
|
expect(json.path).to be_nil
|
|
258
274
|
expect(json.message).to eq 'Failed to write to file: some error'
|
|
275
|
+
expect(described_class.summary_template(result:)).to eq \
|
|
276
|
+
'Failed to write to file: some error'
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
describe 'syntax check integration' do
|
|
280
|
+
let :test_rb_file do
|
|
281
|
+
"./tmp/test_syntax_#{Tins::Token.new(bits: 128)}.rb"
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
after do
|
|
285
|
+
File.delete(test_rb_file) if File.exist?(test_rb_file)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it 'includes syntax_check on valid .rb file (omits clean pass)' do
|
|
289
|
+
expect(chat).to receive(:syntax_checker_for).and_return(nil)
|
|
290
|
+
|
|
291
|
+
tool_call = double(
|
|
292
|
+
'ToolCall',
|
|
293
|
+
function: double(
|
|
294
|
+
name: 'write_file',
|
|
295
|
+
arguments: double(
|
|
296
|
+
path: test_rb_file,
|
|
297
|
+
content: "def foo\n 42\nend\n",
|
|
298
|
+
mode: 'overwrite'
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
304
|
+
json = json_object(result)
|
|
305
|
+
expect(json.success).to eq true
|
|
306
|
+
expect(json.syntax_check).to be_nil
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it 'includes syntax_check fail on broken .rb file' do
|
|
310
|
+
checker = double('checker', cmd: ['ruby', '-wc'])
|
|
311
|
+
expect(chat).to receive(:syntax_checker_for).and_return(checker)
|
|
312
|
+
expect(chat).to receive(:run_syntax_check).with(checker, any_args)
|
|
313
|
+
.and_return({ status: 'fail',
|
|
314
|
+
output: "test.rb:2: syntax error, unexpected 'end'" })
|
|
315
|
+
|
|
316
|
+
tool_call = double(
|
|
317
|
+
'ToolCall',
|
|
318
|
+
function: double(
|
|
319
|
+
name: 'write_file',
|
|
320
|
+
arguments: double(
|
|
321
|
+
path: test_rb_file,
|
|
322
|
+
content: "def foo\nend\nend\n",
|
|
323
|
+
mode: 'overwrite'
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
329
|
+
json = json_object(result)
|
|
330
|
+
expect(json.success).to eq true
|
|
331
|
+
expect(json.message).to include('❌ Syntax error detected')
|
|
332
|
+
expect(json.syntax_check[:status]).to eq 'fail'
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
it 'omits syntax_check for non-matching extension' do
|
|
336
|
+
expect(chat).to receive(:syntax_checker_for).and_return(nil)
|
|
337
|
+
|
|
338
|
+
tool_call = double(
|
|
339
|
+
'ToolCall',
|
|
340
|
+
function: double(
|
|
341
|
+
name: 'write_file',
|
|
342
|
+
arguments: double(
|
|
343
|
+
path: test_write_file,
|
|
344
|
+
content: 'plain text',
|
|
345
|
+
mode: 'overwrite'
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
result = described_class.new.execute(tool_call, chat:)
|
|
351
|
+
json = json_object(result)
|
|
352
|
+
expect(json.success).to eq true
|
|
353
|
+
expect(json.syntax_check).to be_nil
|
|
354
|
+
end
|
|
259
355
|
end
|
|
260
356
|
end
|
|
@@ -35,16 +35,11 @@ describe OllamaChat::Utils::AnalyzeDirectory do
|
|
|
35
35
|
expect(sub[:children].map { |e| e[:depth] }).to eq [ 1 ]
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
it '
|
|
39
|
-
result = generate.call(@tmp_dir, max_depth:
|
|
38
|
+
it 'max_depth 1 prunes all children from directories' do
|
|
39
|
+
result = generate.call(@tmp_dir, max_depth: 1)
|
|
40
40
|
|
|
41
|
-
expect(result).to be_an(Array)
|
|
42
41
|
expect(result.map { |e| e[:name] }).to contain_exactly('a.txt', 'b.rb', 'sub')
|
|
43
|
-
|
|
44
42
|
sub = result.find { |e| e[:name] == 'sub' }
|
|
45
|
-
expect(sub[:depth]).to eq 0
|
|
46
|
-
expect(sub[:type]).to eq('directory')
|
|
47
|
-
expect(sub[:children]).to be_an(Array)
|
|
48
43
|
expect(sub[:children]).to be_empty
|
|
49
44
|
end
|
|
50
45
|
|
|
@@ -55,6 +50,50 @@ describe OllamaChat::Utils::AnalyzeDirectory do
|
|
|
55
50
|
expect(names).to include('example.rb')
|
|
56
51
|
expect(names).not_to include('examplle.json', 'example.md')
|
|
57
52
|
end
|
|
53
|
+
|
|
54
|
+
context 'max_depth with nested directories' do
|
|
55
|
+
before do
|
|
56
|
+
@tmp_dir = Dir.mktmpdir
|
|
57
|
+
@sub_dir = File.join(@tmp_dir, 'sub')
|
|
58
|
+
@deep_dir = File.join(@sub_dir, 'deep')
|
|
59
|
+
Dir.mkdir(@sub_dir)
|
|
60
|
+
Dir.mkdir(@deep_dir)
|
|
61
|
+
@file_a = File.join(@tmp_dir, 'a.txt')
|
|
62
|
+
@file_b = File.join(@sub_dir, 'b.txt')
|
|
63
|
+
@file_c = File.join(@deep_dir, 'c.txt')
|
|
64
|
+
|
|
65
|
+
File.write(@file_a, 'a')
|
|
66
|
+
File.write(@file_b, 'b')
|
|
67
|
+
File.write(@file_c, 'c')
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'max_depth 1 shows only immediate children' do
|
|
71
|
+
result = generate.call(@tmp_dir, max_depth: 1)
|
|
72
|
+
|
|
73
|
+
expect(result.map { |e| e[:name] }).to contain_exactly('a.txt', 'sub')
|
|
74
|
+
sub = result.find { |e| e[:name] == 'sub' }
|
|
75
|
+
expect(sub[:children]).to be_empty
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'max_depth 2 shows children and grandchildren' do
|
|
79
|
+
result = generate.call(@tmp_dir, max_depth: 2)
|
|
80
|
+
|
|
81
|
+
expect(result.map { |e| e[:name] }).to contain_exactly('a.txt', 'sub')
|
|
82
|
+
sub = result.find { |e| e[:name] == 'sub' }
|
|
83
|
+
deep = sub[:children].find { |e| e[:name] == 'deep' }
|
|
84
|
+
|
|
85
|
+
expect(sub[:children].map { |e| e[:name] })
|
|
86
|
+
.to contain_exactly('b.txt', 'deep')
|
|
87
|
+
expect(deep[:children]).to be_empty
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'max_depth exceeding height returns the full tree' do
|
|
91
|
+
full = generate.call(@tmp_dir)
|
|
92
|
+
pruned = generate.call(@tmp_dir, max_depth: 99)
|
|
93
|
+
|
|
94
|
+
expect(pruned.to_json).to eq full.to_json
|
|
95
|
+
end
|
|
96
|
+
end
|
|
58
97
|
end
|
|
59
98
|
|
|
60
99
|
context 'skipping hidden files and symlinks' do
|