tsikol 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/CHANGELOG.md +22 -0
- data/CONTRIBUTING.md +84 -0
- data/LICENSE +21 -0
- data/README.md +579 -0
- data/Rakefile +12 -0
- data/docs/README.md +69 -0
- data/docs/api/middleware.md +721 -0
- data/docs/api/prompt.md +858 -0
- data/docs/api/resource.md +651 -0
- data/docs/api/server.md +509 -0
- data/docs/api/test-helpers.md +591 -0
- data/docs/api/tool.md +527 -0
- data/docs/cookbook/authentication.md +651 -0
- data/docs/cookbook/caching.md +877 -0
- data/docs/cookbook/dynamic-tools.md +970 -0
- data/docs/cookbook/error-handling.md +887 -0
- data/docs/cookbook/logging.md +1044 -0
- data/docs/cookbook/rate-limiting.md +717 -0
- data/docs/examples/code-assistant.md +922 -0
- data/docs/examples/complete-server.md +726 -0
- data/docs/examples/database-manager.md +1198 -0
- data/docs/examples/devops-tools.md +1382 -0
- data/docs/examples/echo-server.md +501 -0
- data/docs/examples/weather-service.md +822 -0
- data/docs/guides/completion.md +472 -0
- data/docs/guides/getting-started.md +462 -0
- data/docs/guides/middleware.md +823 -0
- data/docs/guides/project-structure.md +434 -0
- data/docs/guides/prompts.md +920 -0
- data/docs/guides/resources.md +720 -0
- data/docs/guides/sampling.md +804 -0
- data/docs/guides/testing.md +863 -0
- data/docs/guides/tools.md +627 -0
- data/examples/README.md +92 -0
- data/examples/advanced_features.rb +129 -0
- data/examples/basic-migrated/app/prompts/weather_chat.rb +44 -0
- data/examples/basic-migrated/app/resources/weather_alerts.rb +18 -0
- data/examples/basic-migrated/app/tools/get_current_weather.rb +34 -0
- data/examples/basic-migrated/app/tools/get_forecast.rb +30 -0
- data/examples/basic-migrated/app/tools/get_weather_by_coords.rb +48 -0
- data/examples/basic-migrated/server.rb +25 -0
- data/examples/basic.rb +73 -0
- data/examples/full_featured.rb +175 -0
- data/examples/middleware_example.rb +112 -0
- data/examples/sampling_example.rb +104 -0
- data/examples/weather-service/app/prompts/weather/chat.rb +90 -0
- data/examples/weather-service/app/resources/weather/alerts.rb +59 -0
- data/examples/weather-service/app/tools/weather/get_current.rb +82 -0
- data/examples/weather-service/app/tools/weather/get_forecast.rb +90 -0
- data/examples/weather-service/server.rb +28 -0
- data/exe/tsikol +6 -0
- data/lib/tsikol/cli/templates/Gemfile.erb +10 -0
- data/lib/tsikol/cli/templates/README.md.erb +38 -0
- data/lib/tsikol/cli/templates/gitignore.erb +49 -0
- data/lib/tsikol/cli/templates/prompt.rb.erb +53 -0
- data/lib/tsikol/cli/templates/resource.rb.erb +29 -0
- data/lib/tsikol/cli/templates/server.rb.erb +24 -0
- data/lib/tsikol/cli/templates/tool.rb.erb +60 -0
- data/lib/tsikol/cli.rb +203 -0
- data/lib/tsikol/error_handler.rb +141 -0
- data/lib/tsikol/health.rb +198 -0
- data/lib/tsikol/http_transport.rb +72 -0
- data/lib/tsikol/lifecycle.rb +149 -0
- data/lib/tsikol/middleware.rb +168 -0
- data/lib/tsikol/prompt.rb +101 -0
- data/lib/tsikol/resource.rb +53 -0
- data/lib/tsikol/router.rb +190 -0
- data/lib/tsikol/server.rb +660 -0
- data/lib/tsikol/stdio_transport.rb +108 -0
- data/lib/tsikol/test_helpers.rb +261 -0
- data/lib/tsikol/tool.rb +111 -0
- data/lib/tsikol/version.rb +5 -0
- data/lib/tsikol.rb +72 -0
- metadata +219 -0
@@ -0,0 +1,922 @@
|
|
1
|
+
# Code Assistant Example
|
2
|
+
|
3
|
+
An intelligent MCP server that provides code analysis, generation, and refactoring capabilities with AI-powered features.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
This example demonstrates:
|
8
|
+
- File system operations
|
9
|
+
- Code analysis and parsing
|
10
|
+
- AI-powered code generation (sampling)
|
11
|
+
- Syntax highlighting
|
12
|
+
- Git integration
|
13
|
+
- Code completion
|
14
|
+
- Advanced prompt engineering
|
15
|
+
|
16
|
+
## Implementation
|
17
|
+
|
18
|
+
### server.rb
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
#!/usr/bin/env ruby
|
22
|
+
|
23
|
+
require 'tsikol'
|
24
|
+
require 'parser/current'
|
25
|
+
require 'syntax_tree'
|
26
|
+
require 'rugged'
|
27
|
+
require 'rouge'
|
28
|
+
|
29
|
+
# Code analysis tool
|
30
|
+
class CodeAnalyzerTool < Tsikol::Tool
|
31
|
+
name "analyze_code"
|
32
|
+
description "Analyze code for quality, complexity, and potential issues"
|
33
|
+
|
34
|
+
parameter :file_path do
|
35
|
+
type :string
|
36
|
+
required
|
37
|
+
description "Path to the file to analyze"
|
38
|
+
|
39
|
+
complete do |partial|
|
40
|
+
Dir.glob("#{partial}*").select { |f| File.file?(f) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
parameter :checks do
|
45
|
+
type :array
|
46
|
+
optional
|
47
|
+
default ["complexity", "style", "security"]
|
48
|
+
description "Types of checks to perform"
|
49
|
+
end
|
50
|
+
|
51
|
+
def execute(file_path:, checks: ["complexity", "style", "security"])
|
52
|
+
unless File.exist?(file_path)
|
53
|
+
raise ArgumentError, "File not found: #{file_path}"
|
54
|
+
end
|
55
|
+
|
56
|
+
content = File.read(file_path)
|
57
|
+
language = detect_language(file_path)
|
58
|
+
|
59
|
+
results = {
|
60
|
+
file: file_path,
|
61
|
+
language: language,
|
62
|
+
metrics: analyze_metrics(content, language),
|
63
|
+
issues: []
|
64
|
+
}
|
65
|
+
|
66
|
+
# Perform requested checks
|
67
|
+
checks.each do |check|
|
68
|
+
case check
|
69
|
+
when "complexity"
|
70
|
+
results[:issues].concat(check_complexity(content, language))
|
71
|
+
when "style"
|
72
|
+
results[:issues].concat(check_style(content, language))
|
73
|
+
when "security"
|
74
|
+
results[:issues].concat(check_security(content, language))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
format_analysis_results(results)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def detect_language(file_path)
|
84
|
+
case File.extname(file_path)
|
85
|
+
when ".rb" then "ruby"
|
86
|
+
when ".py" then "python"
|
87
|
+
when ".js" then "javascript"
|
88
|
+
when ".java" then "java"
|
89
|
+
when ".go" then "go"
|
90
|
+
else "unknown"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def analyze_metrics(content, language)
|
95
|
+
lines = content.lines
|
96
|
+
|
97
|
+
{
|
98
|
+
lines_of_code: lines.count,
|
99
|
+
blank_lines: lines.count(&:blank?),
|
100
|
+
comment_lines: count_comment_lines(lines, language),
|
101
|
+
average_line_length: lines.map(&:length).sum.to_f / lines.count
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
def check_complexity(content, language)
|
106
|
+
issues = []
|
107
|
+
|
108
|
+
if language == "ruby"
|
109
|
+
ast = Parser::CurrentRuby.parse(content)
|
110
|
+
|
111
|
+
# Check method complexity
|
112
|
+
processor = ComplexityProcessor.new
|
113
|
+
processor.process(ast)
|
114
|
+
|
115
|
+
processor.complex_methods.each do |method|
|
116
|
+
issues << {
|
117
|
+
type: "complexity",
|
118
|
+
severity: "warning",
|
119
|
+
line: method[:line],
|
120
|
+
message: "Method '#{method[:name]}' has cyclomatic complexity of #{method[:complexity]} (threshold: 10)"
|
121
|
+
}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
issues
|
126
|
+
end
|
127
|
+
|
128
|
+
def format_analysis_results(results)
|
129
|
+
output = ["Code Analysis Results", "=" * 40, ""]
|
130
|
+
|
131
|
+
output << "File: #{results[:file]}"
|
132
|
+
output << "Language: #{results[:language]}"
|
133
|
+
output << ""
|
134
|
+
output << "Metrics:"
|
135
|
+
results[:metrics].each do |key, value|
|
136
|
+
output << " #{key.to_s.gsub('_', ' ').capitalize}: #{value.is_a?(Float) ? value.round(2) : value}"
|
137
|
+
end
|
138
|
+
|
139
|
+
if results[:issues].any?
|
140
|
+
output << ""
|
141
|
+
output << "Issues Found:"
|
142
|
+
results[:issues].group_by { |i| i[:type] }.each do |type, issues|
|
143
|
+
output << ""
|
144
|
+
output << " #{type.capitalize}:"
|
145
|
+
issues.each do |issue|
|
146
|
+
output << " Line #{issue[:line]}: [#{issue[:severity]}] #{issue[:message]}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
else
|
150
|
+
output << ""
|
151
|
+
output << "No issues found!"
|
152
|
+
end
|
153
|
+
|
154
|
+
output.join("\n")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Code generation tool with sampling
|
159
|
+
class CodeGeneratorTool < Tsikol::Tool
|
160
|
+
name "generate_code"
|
161
|
+
description "Generate code based on specifications using AI"
|
162
|
+
|
163
|
+
# Enable sampling for this tool
|
164
|
+
sampling true
|
165
|
+
|
166
|
+
parameter :specification do
|
167
|
+
type :string
|
168
|
+
required
|
169
|
+
description "Description of what code to generate"
|
170
|
+
end
|
171
|
+
|
172
|
+
parameter :language do
|
173
|
+
type :string
|
174
|
+
required
|
175
|
+
enum ["ruby", "python", "javascript", "java", "go"]
|
176
|
+
description "Target programming language"
|
177
|
+
end
|
178
|
+
|
179
|
+
parameter :style do
|
180
|
+
type :string
|
181
|
+
optional
|
182
|
+
default "clean"
|
183
|
+
enum ["clean", "documented", "compact", "enterprise"]
|
184
|
+
description "Code style preference"
|
185
|
+
end
|
186
|
+
|
187
|
+
parameter :include_tests do
|
188
|
+
type :boolean
|
189
|
+
optional
|
190
|
+
default false
|
191
|
+
description "Include unit tests"
|
192
|
+
end
|
193
|
+
|
194
|
+
def execute(specification:, language:, style: "clean", include_tests: false)
|
195
|
+
# Use AI sampling to generate code
|
196
|
+
messages = build_generation_messages(specification, language, style, include_tests)
|
197
|
+
|
198
|
+
response = @server.sample_text(
|
199
|
+
messages: messages,
|
200
|
+
max_tokens: 2000,
|
201
|
+
temperature: 0.3 # Lower temperature for more consistent code
|
202
|
+
)
|
203
|
+
|
204
|
+
if response[:error]
|
205
|
+
raise "Code generation failed: #{response[:error]}"
|
206
|
+
end
|
207
|
+
|
208
|
+
generated_code = response[:text]
|
209
|
+
|
210
|
+
# Format and validate generated code
|
211
|
+
formatted_code = format_code(generated_code, language)
|
212
|
+
|
213
|
+
if include_tests
|
214
|
+
test_code = generate_tests(specification, formatted_code, language)
|
215
|
+
formatted_code += "\n\n" + test_code
|
216
|
+
end
|
217
|
+
|
218
|
+
formatted_code
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def build_generation_messages(spec, language, style, include_tests)
|
224
|
+
system_prompt = build_system_prompt(language, style)
|
225
|
+
|
226
|
+
user_prompt = <<~PROMPT
|
227
|
+
Generate #{language} code for the following specification:
|
228
|
+
|
229
|
+
#{spec}
|
230
|
+
|
231
|
+
Requirements:
|
232
|
+
- Follow #{style} coding style
|
233
|
+
- Include error handling
|
234
|
+
- Add appropriate comments
|
235
|
+
#{include_tests ? "- Include comprehensive unit tests" : ""}
|
236
|
+
PROMPT
|
237
|
+
|
238
|
+
[
|
239
|
+
{
|
240
|
+
role: "system",
|
241
|
+
content: { type: "text", text: system_prompt }
|
242
|
+
},
|
243
|
+
{
|
244
|
+
role: "user",
|
245
|
+
content: { type: "text", text: user_prompt }
|
246
|
+
}
|
247
|
+
]
|
248
|
+
end
|
249
|
+
|
250
|
+
def build_system_prompt(language, style)
|
251
|
+
base = "You are an expert #{language} developer. Generate high-quality, production-ready code."
|
252
|
+
|
253
|
+
style_guides = {
|
254
|
+
"clean" => "Follow clean code principles: clear naming, small functions, minimal complexity.",
|
255
|
+
"documented" => "Include comprehensive documentation: docstrings, inline comments, usage examples.",
|
256
|
+
"compact" => "Write concise code while maintaining readability. Avoid unnecessary verbosity.",
|
257
|
+
"enterprise" => "Follow enterprise patterns: interfaces, dependency injection, comprehensive error handling."
|
258
|
+
}
|
259
|
+
|
260
|
+
"#{base} #{style_guides[style]}"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Code refactoring tool
|
265
|
+
class CodeRefactorTool < Tsikol::Tool
|
266
|
+
name "refactor_code"
|
267
|
+
description "Refactor code to improve quality and maintainability"
|
268
|
+
|
269
|
+
parameter :file_path do
|
270
|
+
type :string
|
271
|
+
required
|
272
|
+
description "Path to file to refactor"
|
273
|
+
end
|
274
|
+
|
275
|
+
parameter :refactoring_type do
|
276
|
+
type :string
|
277
|
+
required
|
278
|
+
enum ["extract_method", "rename", "simplify", "dry", "modernize"]
|
279
|
+
description "Type of refactoring to perform"
|
280
|
+
end
|
281
|
+
|
282
|
+
parameter :options do
|
283
|
+
type :object
|
284
|
+
optional
|
285
|
+
default {}
|
286
|
+
description "Refactoring-specific options"
|
287
|
+
end
|
288
|
+
|
289
|
+
def execute(file_path:, refactoring_type:, options: {})
|
290
|
+
unless File.exist?(file_path)
|
291
|
+
raise ArgumentError, "File not found: #{file_path}"
|
292
|
+
end
|
293
|
+
|
294
|
+
content = File.read(file_path)
|
295
|
+
language = detect_language(file_path)
|
296
|
+
|
297
|
+
refactored = case refactoring_type
|
298
|
+
when "extract_method"
|
299
|
+
extract_method(content, language, options)
|
300
|
+
when "rename"
|
301
|
+
rename_symbols(content, language, options)
|
302
|
+
when "simplify"
|
303
|
+
simplify_code(content, language)
|
304
|
+
when "dry"
|
305
|
+
remove_duplication(content, language)
|
306
|
+
when "modernize"
|
307
|
+
modernize_code(content, language)
|
308
|
+
end
|
309
|
+
|
310
|
+
# Show diff
|
311
|
+
format_refactoring_result(content, refactored, refactoring_type)
|
312
|
+
end
|
313
|
+
|
314
|
+
private
|
315
|
+
|
316
|
+
def extract_method(content, language, options)
|
317
|
+
start_line = options["start_line"] || raise("start_line required")
|
318
|
+
end_line = options["end_line"] || raise("end_line required")
|
319
|
+
method_name = options["method_name"] || "extracted_method"
|
320
|
+
|
321
|
+
lines = content.lines
|
322
|
+
extracted = lines[start_line-1..end_line-1]
|
323
|
+
|
324
|
+
# Simple extraction (real implementation would analyze variables)
|
325
|
+
new_method = "\ndef #{method_name}\n#{extracted.map { |l| " #{l}" }.join}\nend\n"
|
326
|
+
|
327
|
+
# Replace with method call
|
328
|
+
lines[start_line-1..end_line-1] = ["#{method_name}\n"]
|
329
|
+
|
330
|
+
lines.join + new_method
|
331
|
+
end
|
332
|
+
|
333
|
+
def format_refactoring_result(original, refactored, type)
|
334
|
+
<<~RESULT
|
335
|
+
Refactoring: #{type}
|
336
|
+
#{"=" * 40}
|
337
|
+
|
338
|
+
Original:
|
339
|
+
#{original}
|
340
|
+
|
341
|
+
#{"=" * 40}
|
342
|
+
|
343
|
+
Refactored:
|
344
|
+
#{refactored}
|
345
|
+
|
346
|
+
#{"=" * 40}
|
347
|
+
|
348
|
+
Summary:
|
349
|
+
- Lines changed: #{count_changed_lines(original, refactored)}
|
350
|
+
- Size difference: #{refactored.length - original.length} chars
|
351
|
+
RESULT
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
# Git integration resource
|
356
|
+
class GitResource < Tsikol::Resource
|
357
|
+
uri "git/:command/:path?"
|
358
|
+
description "Git repository information and operations"
|
359
|
+
|
360
|
+
def read
|
361
|
+
command = params[:command]
|
362
|
+
path = params[:path] || "."
|
363
|
+
|
364
|
+
repo = Rugged::Repository.new(path)
|
365
|
+
|
366
|
+
case command
|
367
|
+
when "status"
|
368
|
+
git_status(repo)
|
369
|
+
when "log"
|
370
|
+
git_log(repo)
|
371
|
+
when "branches"
|
372
|
+
git_branches(repo)
|
373
|
+
when "diff"
|
374
|
+
git_diff(repo)
|
375
|
+
else
|
376
|
+
{ error: "Unknown git command: #{command}" }.to_json
|
377
|
+
end
|
378
|
+
rescue Rugged::RepositoryError => e
|
379
|
+
{ error: "Git error: #{e.message}" }.to_json
|
380
|
+
end
|
381
|
+
|
382
|
+
private
|
383
|
+
|
384
|
+
def git_status(repo)
|
385
|
+
status = {
|
386
|
+
branch: repo.head.name.sub('refs/heads/', ''),
|
387
|
+
ahead: 0, # Would need to compare with upstream
|
388
|
+
behind: 0,
|
389
|
+
modified: [],
|
390
|
+
untracked: [],
|
391
|
+
staged: []
|
392
|
+
}
|
393
|
+
|
394
|
+
repo.status_file_each do |file, status_flags|
|
395
|
+
if status_flags.include?(:worktree_modified)
|
396
|
+
status[:modified] << file
|
397
|
+
elsif status_flags.include?(:worktree_new)
|
398
|
+
status[:untracked] << file
|
399
|
+
elsif status_flags.include?(:index_modified) || status_flags.include?(:index_new)
|
400
|
+
status[:staged] << file
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
status.to_json
|
405
|
+
end
|
406
|
+
|
407
|
+
def git_log(repo, limit = 20)
|
408
|
+
walker = Rugged::Walker.new(repo)
|
409
|
+
walker.push(repo.head.target_id)
|
410
|
+
|
411
|
+
commits = walker.take(limit).map do |commit|
|
412
|
+
{
|
413
|
+
sha: commit.oid[0..7],
|
414
|
+
message: commit.message.lines.first.strip,
|
415
|
+
author: commit.author[:name],
|
416
|
+
date: commit.author[:time].iso8601
|
417
|
+
}
|
418
|
+
end
|
419
|
+
|
420
|
+
{ commits: commits }.to_json
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# Code completion provider
|
425
|
+
class CodeCompletionResource < Tsikol::Resource
|
426
|
+
uri "completion/:language/:context"
|
427
|
+
description "Provide code completion suggestions"
|
428
|
+
|
429
|
+
# Enable completion capability
|
430
|
+
completion true
|
431
|
+
|
432
|
+
def complete
|
433
|
+
language = params[:language]
|
434
|
+
context = params[:context]
|
435
|
+
partial = params[:partial] || ""
|
436
|
+
|
437
|
+
suggestions = case language
|
438
|
+
when "ruby"
|
439
|
+
ruby_completions(context, partial)
|
440
|
+
when "python"
|
441
|
+
python_completions(context, partial)
|
442
|
+
else
|
443
|
+
[]
|
444
|
+
end
|
445
|
+
|
446
|
+
{
|
447
|
+
completion: "completion",
|
448
|
+
values: suggestions.map { |s| { value: s[:text], label: s[:label] } }
|
449
|
+
}
|
450
|
+
end
|
451
|
+
|
452
|
+
private
|
453
|
+
|
454
|
+
def ruby_completions(context, partial)
|
455
|
+
case context
|
456
|
+
when "method"
|
457
|
+
%w[def private protected public attr_reader attr_writer attr_accessor].select { |m| m.start_with?(partial) }
|
458
|
+
.map { |m| { text: m, label: "#{m} (keyword)" } }
|
459
|
+
when "class"
|
460
|
+
%w[class module include extend prepend].select { |k| k.start_with?(partial) }
|
461
|
+
.map { |k| { text: k, label: "#{k} (keyword)" } }
|
462
|
+
else
|
463
|
+
[]
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
# Code assistant prompt
|
469
|
+
class CodeAssistantPrompt < Tsikol::Prompt
|
470
|
+
name "code_assistant"
|
471
|
+
description "Expert programming assistant with deep knowledge of multiple languages"
|
472
|
+
|
473
|
+
argument :expertise do
|
474
|
+
type :string
|
475
|
+
required
|
476
|
+
enum ["general", "web", "systems", "data_science", "mobile", "devops"]
|
477
|
+
description "Area of expertise to focus on"
|
478
|
+
end
|
479
|
+
|
480
|
+
argument :languages do
|
481
|
+
type :array
|
482
|
+
optional
|
483
|
+
default ["ruby", "python", "javascript"]
|
484
|
+
description "Preferred programming languages"
|
485
|
+
end
|
486
|
+
|
487
|
+
argument :level do
|
488
|
+
type :string
|
489
|
+
optional
|
490
|
+
default "intermediate"
|
491
|
+
enum ["beginner", "intermediate", "advanced", "expert"]
|
492
|
+
description "User's skill level"
|
493
|
+
end
|
494
|
+
|
495
|
+
def get_messages(expertise:, languages: ["ruby", "python", "javascript"], level: "intermediate")
|
496
|
+
system = build_system_message(expertise, languages, level)
|
497
|
+
|
498
|
+
[
|
499
|
+
{
|
500
|
+
role: "system",
|
501
|
+
content: { type: "text", text: system }
|
502
|
+
},
|
503
|
+
{
|
504
|
+
role: "user",
|
505
|
+
content: { type: "text", text: "Initialize code assistant" }
|
506
|
+
},
|
507
|
+
{
|
508
|
+
role: "assistant",
|
509
|
+
content: {
|
510
|
+
type: "text",
|
511
|
+
text: "I'm ready to help with your #{expertise} programming needs. I specialize in #{languages.join(', ')} and will adjust my explanations for #{level} level understanding. I can analyze code, generate solutions, suggest refactorings, and help with debugging. What would you like to work on?"
|
512
|
+
}
|
513
|
+
}
|
514
|
+
]
|
515
|
+
end
|
516
|
+
|
517
|
+
private
|
518
|
+
|
519
|
+
def build_system_message(expertise, languages, level)
|
520
|
+
base = <<~SYSTEM
|
521
|
+
You are an expert programming assistant specializing in #{expertise} development.
|
522
|
+
|
523
|
+
Primary languages: #{languages.join(', ')}
|
524
|
+
User level: #{level}
|
525
|
+
|
526
|
+
Available tools:
|
527
|
+
- analyze_code: Analyze code quality and find issues
|
528
|
+
- generate_code: Generate new code from specifications
|
529
|
+
- refactor_code: Improve existing code
|
530
|
+
|
531
|
+
Key capabilities:
|
532
|
+
1. Write clean, efficient, and maintainable code
|
533
|
+
2. Explain complex concepts clearly
|
534
|
+
3. Debug issues systematically
|
535
|
+
4. Suggest best practices and patterns
|
536
|
+
5. Provide performance optimization advice
|
537
|
+
|
538
|
+
Guidelines:
|
539
|
+
- Adjust explanations to #{level} level
|
540
|
+
- Always consider security implications
|
541
|
+
- Prefer readability over cleverness
|
542
|
+
- Include error handling in all code
|
543
|
+
- Follow language-specific conventions
|
544
|
+
SYSTEM
|
545
|
+
|
546
|
+
base + expertise_specific_guidance(expertise)
|
547
|
+
end
|
548
|
+
|
549
|
+
def expertise_specific_guidance(expertise)
|
550
|
+
case expertise
|
551
|
+
when "web"
|
552
|
+
"\nWeb Development Focus:\n- Modern frameworks and libraries\n- RESTful API design\n- Frontend/backend integration\n- Security (XSS, CSRF, etc.)\n- Performance optimization"
|
553
|
+
when "systems"
|
554
|
+
"\nSystems Programming Focus:\n- Memory management\n- Concurrency and parallelism\n- Low-level optimization\n- Operating system interfaces\n- Network programming"
|
555
|
+
when "data_science"
|
556
|
+
"\nData Science Focus:\n- Data manipulation and analysis\n- Machine learning algorithms\n- Statistical methods\n- Visualization techniques\n- Big data processing"
|
557
|
+
else
|
558
|
+
""
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
# Syntax highlighting resource
|
564
|
+
class SyntaxHighlightResource < Tsikol::Resource
|
565
|
+
uri "highlight/:file_path"
|
566
|
+
description "Syntax highlighted code"
|
567
|
+
|
568
|
+
def read
|
569
|
+
file_path = params[:file_path]
|
570
|
+
theme = params[:theme] || "monokai"
|
571
|
+
|
572
|
+
unless File.exist?(file_path)
|
573
|
+
return { error: "File not found" }.to_json
|
574
|
+
end
|
575
|
+
|
576
|
+
content = File.read(file_path)
|
577
|
+
language = detect_language(file_path)
|
578
|
+
|
579
|
+
lexer = Rouge::Lexer.find(language) || Rouge::Lexers::PlainText
|
580
|
+
formatter = Rouge::Formatters::HTML.new
|
581
|
+
|
582
|
+
highlighted = formatter.format(lexer.lex(content))
|
583
|
+
|
584
|
+
{
|
585
|
+
file: file_path,
|
586
|
+
language: language,
|
587
|
+
theme: theme,
|
588
|
+
html: highlighted,
|
589
|
+
css: Rouge::Themes::Monokai.render(scope: '.highlight')
|
590
|
+
}.to_json
|
591
|
+
end
|
592
|
+
|
593
|
+
private
|
594
|
+
|
595
|
+
def detect_language(file_path)
|
596
|
+
Rouge::Lexer.guess_by_filename(file_path)&.tag || "plaintext"
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
# Complexity analysis processor
|
601
|
+
class ComplexityProcessor < Parser::AST::Processor
|
602
|
+
attr_reader :complex_methods
|
603
|
+
|
604
|
+
def initialize
|
605
|
+
@complex_methods = []
|
606
|
+
@complexity_stack = []
|
607
|
+
end
|
608
|
+
|
609
|
+
def on_def(node)
|
610
|
+
method_name = node.children[0]
|
611
|
+
@complexity_stack.push({ name: method_name, complexity: 1, line: node.location.line })
|
612
|
+
super
|
613
|
+
|
614
|
+
method_info = @complexity_stack.pop
|
615
|
+
if method_info[:complexity] > 10
|
616
|
+
@complex_methods << method_info
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
def on_if(node)
|
621
|
+
@complexity_stack.last[:complexity] += 1 if @complexity_stack.any?
|
622
|
+
super
|
623
|
+
end
|
624
|
+
|
625
|
+
alias on_case on_if
|
626
|
+
alias on_when on_if
|
627
|
+
alias on_while on_if
|
628
|
+
alias on_until on_if
|
629
|
+
alias on_for on_if
|
630
|
+
end
|
631
|
+
|
632
|
+
# Start the server
|
633
|
+
Tsikol.start(
|
634
|
+
name: "code-assistant",
|
635
|
+
version: "1.0.0",
|
636
|
+
description: "Intelligent code analysis and generation assistant"
|
637
|
+
) do
|
638
|
+
# Enable all capabilities
|
639
|
+
logging true
|
640
|
+
prompts true
|
641
|
+
sampling true
|
642
|
+
completion true
|
643
|
+
|
644
|
+
# Middleware
|
645
|
+
use Tsikol::LoggingMiddleware, level: :info
|
646
|
+
use Tsikol::AuthenticationMiddleware,
|
647
|
+
type: :bearer,
|
648
|
+
validator: ->(token) { validate_api_token(token) }
|
649
|
+
|
650
|
+
# Tools
|
651
|
+
tool CodeAnalyzerTool
|
652
|
+
tool CodeGeneratorTool
|
653
|
+
tool CodeRefactorTool
|
654
|
+
|
655
|
+
# Resources
|
656
|
+
resource GitResource
|
657
|
+
resource CodeCompletionResource
|
658
|
+
resource SyntaxHighlightResource
|
659
|
+
|
660
|
+
# Prompts
|
661
|
+
prompt CodeAssistantPrompt
|
662
|
+
|
663
|
+
# Helper methods
|
664
|
+
def validate_api_token(token)
|
665
|
+
# In production, validate against database
|
666
|
+
return { user_id: "demo", tier: "premium" } if token == "demo-token"
|
667
|
+
nil
|
668
|
+
end
|
669
|
+
end
|
670
|
+
```
|
671
|
+
|
672
|
+
### Testing
|
673
|
+
|
674
|
+
```ruby
|
675
|
+
require 'minitest/autorun'
|
676
|
+
require 'tsikol/test_helpers'
|
677
|
+
|
678
|
+
class CodeAssistantTest < Minitest::Test
|
679
|
+
include Tsikol::TestHelpers
|
680
|
+
|
681
|
+
def setup
|
682
|
+
@server = create_test_server
|
683
|
+
@client = TestClient.new(@server)
|
684
|
+
|
685
|
+
# Create test files
|
686
|
+
setup_test_files
|
687
|
+
end
|
688
|
+
|
689
|
+
def teardown
|
690
|
+
cleanup_test_files
|
691
|
+
end
|
692
|
+
|
693
|
+
def test_code_analysis
|
694
|
+
response = @client.call_tool("analyze_code", {
|
695
|
+
"file_path" => "test_code.rb",
|
696
|
+
"checks" => ["complexity", "style"]
|
697
|
+
})
|
698
|
+
|
699
|
+
assert_successful_response(response)
|
700
|
+
result = response.dig(:result, :content, 0, :text)
|
701
|
+
assert_match /Code Analysis Results/, result
|
702
|
+
assert_match /Lines of code:/, result
|
703
|
+
end
|
704
|
+
|
705
|
+
def test_code_generation
|
706
|
+
response = @client.call_tool("generate_code", {
|
707
|
+
"specification" => "Create a function to calculate fibonacci numbers",
|
708
|
+
"language" => "ruby",
|
709
|
+
"style" => "documented"
|
710
|
+
})
|
711
|
+
|
712
|
+
assert_successful_response(response)
|
713
|
+
code = response.dig(:result, :content, 0, :text)
|
714
|
+
assert_match /def fibonacci/, code
|
715
|
+
assert_match /#/, code # Should have comments
|
716
|
+
end
|
717
|
+
|
718
|
+
def test_code_refactoring
|
719
|
+
response = @client.call_tool("refactor_code", {
|
720
|
+
"file_path" => "test_code.rb",
|
721
|
+
"refactoring_type" => "extract_method",
|
722
|
+
"options" => {
|
723
|
+
"start_line" => 3,
|
724
|
+
"end_line" => 5,
|
725
|
+
"method_name" => "calculate_sum"
|
726
|
+
}
|
727
|
+
})
|
728
|
+
|
729
|
+
assert_successful_response(response)
|
730
|
+
result = response.dig(:result, :content, 0, :text)
|
731
|
+
assert_match /def calculate_sum/, result
|
732
|
+
end
|
733
|
+
|
734
|
+
def test_git_integration
|
735
|
+
response = @client.read_resource("git/status")
|
736
|
+
|
737
|
+
assert_successful_response(response)
|
738
|
+
status = JSON.parse(response.dig(:result, :contents, 0, :text))
|
739
|
+
assert status.key?("branch")
|
740
|
+
end
|
741
|
+
|
742
|
+
def test_code_completion
|
743
|
+
response = @client.complete_resource("completion/ruby/method", {
|
744
|
+
"partial" => "att"
|
745
|
+
})
|
746
|
+
|
747
|
+
assert_successful_response(response)
|
748
|
+
completions = response.dig(:result, :completion, :values)
|
749
|
+
assert completions.any? { |c| c[:value] == "attr_reader" }
|
750
|
+
end
|
751
|
+
|
752
|
+
private
|
753
|
+
|
754
|
+
def setup_test_files
|
755
|
+
File.write("test_code.rb", <<~RUBY)
|
756
|
+
class Calculator
|
757
|
+
def complex_method(a, b, c)
|
758
|
+
if a > 0
|
759
|
+
if b > 0
|
760
|
+
if c > 0
|
761
|
+
a + b + c
|
762
|
+
else
|
763
|
+
a + b
|
764
|
+
end
|
765
|
+
else
|
766
|
+
a
|
767
|
+
end
|
768
|
+
else
|
769
|
+
0
|
770
|
+
end
|
771
|
+
end
|
772
|
+
end
|
773
|
+
RUBY
|
774
|
+
|
775
|
+
# Initialize git repo
|
776
|
+
`git init . 2>/dev/null`
|
777
|
+
end
|
778
|
+
|
779
|
+
def cleanup_test_files
|
780
|
+
File.delete("test_code.rb") if File.exist?("test_code.rb")
|
781
|
+
FileUtils.rm_rf(".git")
|
782
|
+
end
|
783
|
+
|
784
|
+
def create_test_server
|
785
|
+
# Mock server for testing
|
786
|
+
Tsikol::Server.new(name: "test-code-assistant") do
|
787
|
+
sampling false # Disable sampling for tests
|
788
|
+
|
789
|
+
tool CodeAnalyzerTool
|
790
|
+
tool CodeGeneratorTool
|
791
|
+
tool CodeRefactorTool
|
792
|
+
resource GitResource
|
793
|
+
resource CodeCompletionResource
|
794
|
+
end
|
795
|
+
end
|
796
|
+
end
|
797
|
+
```
|
798
|
+
|
799
|
+
## Advanced Features
|
800
|
+
|
801
|
+
### IDE Integration
|
802
|
+
|
803
|
+
```ruby
|
804
|
+
# IDE server extension
|
805
|
+
class IDEServer < Tsikol::Server
|
806
|
+
def initialize
|
807
|
+
super(name: "code-assistant-ide")
|
808
|
+
|
809
|
+
# Language Server Protocol support
|
810
|
+
enable_lsp
|
811
|
+
|
812
|
+
# Real-time diagnostics
|
813
|
+
enable_diagnostics
|
814
|
+
|
815
|
+
# Workspace indexing
|
816
|
+
@workspace_index = WorkspaceIndex.new
|
817
|
+
end
|
818
|
+
|
819
|
+
def on_file_change(file_path)
|
820
|
+
# Re-analyze on file change
|
821
|
+
analyze_file_async(file_path)
|
822
|
+
|
823
|
+
# Update diagnostics
|
824
|
+
publish_diagnostics(file_path)
|
825
|
+
end
|
826
|
+
|
827
|
+
def on_completion_request(file_path, position)
|
828
|
+
# Context-aware completion
|
829
|
+
context = analyze_context(file_path, position)
|
830
|
+
provide_completions(context)
|
831
|
+
end
|
832
|
+
end
|
833
|
+
```
|
834
|
+
|
835
|
+
### AI-Powered Features
|
836
|
+
|
837
|
+
```ruby
|
838
|
+
# Advanced AI code review
|
839
|
+
class AICodeReviewTool < Tsikol::Tool
|
840
|
+
name "ai_code_review"
|
841
|
+
description "Comprehensive AI-powered code review"
|
842
|
+
|
843
|
+
sampling true
|
844
|
+
|
845
|
+
def execute(file_path:, focus_areas: [])
|
846
|
+
content = File.read(file_path)
|
847
|
+
|
848
|
+
# Get AI review
|
849
|
+
review = get_ai_review(content, focus_areas)
|
850
|
+
|
851
|
+
# Parse and structure results
|
852
|
+
structure_review_results(review)
|
853
|
+
end
|
854
|
+
|
855
|
+
private
|
856
|
+
|
857
|
+
def get_ai_review(content, focus_areas)
|
858
|
+
messages = [
|
859
|
+
{
|
860
|
+
role: "system",
|
861
|
+
content: {
|
862
|
+
type: "text",
|
863
|
+
text: "You are an expert code reviewer. Review the following code for: #{focus_areas.join(', ')}"
|
864
|
+
}
|
865
|
+
},
|
866
|
+
{
|
867
|
+
role: "user",
|
868
|
+
content: {
|
869
|
+
type: "text",
|
870
|
+
text: "Review this code:\n\n#{content}"
|
871
|
+
}
|
872
|
+
}
|
873
|
+
]
|
874
|
+
|
875
|
+
response = @server.sample_text(
|
876
|
+
messages: messages,
|
877
|
+
max_tokens: 2000,
|
878
|
+
temperature: 0.2
|
879
|
+
)
|
880
|
+
|
881
|
+
response[:text]
|
882
|
+
end
|
883
|
+
end
|
884
|
+
```
|
885
|
+
|
886
|
+
## Best Practices Demonstrated
|
887
|
+
|
888
|
+
1. **Modular Architecture** - Separate tools for different functions
|
889
|
+
2. **AI Integration** - Smart use of sampling for code generation
|
890
|
+
3. **Language Agnostic** - Support for multiple programming languages
|
891
|
+
4. **Git Integration** - Version control awareness
|
892
|
+
5. **Real-time Features** - Code completion and diagnostics
|
893
|
+
6. **Security** - Authentication and input validation
|
894
|
+
7. **Performance** - Efficient code analysis with AST parsing
|
895
|
+
8. **Testing** - Comprehensive test coverage
|
896
|
+
9. **Error Handling** - Graceful degradation
|
897
|
+
10. **Documentation** - Self-documenting code with examples
|
898
|
+
|
899
|
+
## Performance Considerations
|
900
|
+
|
901
|
+
1. **AST Caching** - Cache parsed ASTs for repeated analysis
|
902
|
+
2. **Incremental Analysis** - Only re-analyze changed parts
|
903
|
+
3. **Background Processing** - Async analysis for large files
|
904
|
+
4. **Index Optimization** - Pre-index workspace for fast search
|
905
|
+
5. **Streaming Results** - Stream large analysis results
|
906
|
+
|
907
|
+
## Security Notes
|
908
|
+
|
909
|
+
1. **Sandboxed Execution** - Never execute untrusted code
|
910
|
+
2. **Path Validation** - Prevent directory traversal
|
911
|
+
3. **Rate Limiting** - Prevent DoS on expensive operations
|
912
|
+
4. **Authentication** - Require auth for sensitive operations
|
913
|
+
5. **Input Sanitization** - Clean all file paths and content
|
914
|
+
|
915
|
+
## Next Steps
|
916
|
+
|
917
|
+
- Add support for more languages
|
918
|
+
- Implement debugging capabilities
|
919
|
+
- Add code metrics dashboard
|
920
|
+
- Create VS Code extension
|
921
|
+
- Add collaborative features
|
922
|
+
- Implement code search across repositories
|