aia 0.9.11 → 0.9.12

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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.version +1 -1
  3. data/CHANGELOG.md +66 -2
  4. data/README.md +133 -4
  5. data/docs/advanced-prompting.md +721 -0
  6. data/docs/cli-reference.md +582 -0
  7. data/docs/configuration.md +347 -0
  8. data/docs/contributing.md +332 -0
  9. data/docs/directives-reference.md +490 -0
  10. data/docs/examples/index.md +277 -0
  11. data/docs/examples/mcp/index.md +479 -0
  12. data/docs/examples/prompts/analysis/index.md +78 -0
  13. data/docs/examples/prompts/automation/index.md +108 -0
  14. data/docs/examples/prompts/development/index.md +125 -0
  15. data/docs/examples/prompts/index.md +333 -0
  16. data/docs/examples/prompts/learning/index.md +127 -0
  17. data/docs/examples/prompts/writing/index.md +62 -0
  18. data/docs/examples/tools/index.md +292 -0
  19. data/docs/faq.md +414 -0
  20. data/docs/guides/available-models.md +366 -0
  21. data/docs/guides/basic-usage.md +477 -0
  22. data/docs/guides/chat.md +474 -0
  23. data/docs/guides/executable-prompts.md +417 -0
  24. data/docs/guides/first-prompt.md +454 -0
  25. data/docs/guides/getting-started.md +455 -0
  26. data/docs/guides/image-generation.md +507 -0
  27. data/docs/guides/index.md +46 -0
  28. data/docs/guides/models.md +507 -0
  29. data/docs/guides/tools.md +856 -0
  30. data/docs/index.md +173 -0
  31. data/docs/installation.md +238 -0
  32. data/docs/mcp-integration.md +612 -0
  33. data/docs/prompt_management.md +579 -0
  34. data/docs/security.md +629 -0
  35. data/docs/tools-and-mcp-examples.md +1186 -0
  36. data/docs/workflows-and-pipelines.md +563 -0
  37. data/examples/tools/mcp/github_mcp_server.json +11 -0
  38. data/examples/tools/mcp/imcp.json +7 -0
  39. data/lib/aia/chat_processor_service.rb +19 -3
  40. data/lib/aia/config/base.rb +224 -0
  41. data/lib/aia/config/cli_parser.rb +409 -0
  42. data/lib/aia/config/defaults.rb +88 -0
  43. data/lib/aia/config/file_loader.rb +131 -0
  44. data/lib/aia/config/validator.rb +184 -0
  45. data/lib/aia/config.rb +10 -860
  46. data/lib/aia/directive_processor.rb +27 -372
  47. data/lib/aia/directives/configuration.rb +114 -0
  48. data/lib/aia/directives/execution.rb +37 -0
  49. data/lib/aia/directives/models.rb +178 -0
  50. data/lib/aia/directives/registry.rb +120 -0
  51. data/lib/aia/directives/utility.rb +70 -0
  52. data/lib/aia/directives/web_and_file.rb +71 -0
  53. data/lib/aia/prompt_handler.rb +23 -3
  54. data/lib/aia/ruby_llm_adapter.rb +307 -128
  55. data/lib/aia/session.rb +27 -14
  56. data/lib/aia/utility.rb +12 -8
  57. data/lib/aia.rb +11 -2
  58. data/lib/extensions/ruby_llm/.irbrc +56 -0
  59. data/mkdocs.yml +165 -0
  60. metadata +77 -20
  61. /data/{images → docs/assets/images}/aia.png +0 -0
@@ -0,0 +1,1186 @@
1
+ # Tools and MCP Examples
2
+
3
+ This comprehensive collection showcases real-world examples of RubyLLM tools and MCP client integrations, demonstrating practical applications and advanced techniques.
4
+
5
+ ## Real-World Tool Examples
6
+
7
+ ### File Processing Tools
8
+
9
+ #### Advanced Log Analyzer
10
+ ```ruby
11
+ # ~/.aia/tools/log_analyzer.rb
12
+ require 'time'
13
+ require 'json'
14
+
15
+ class LogAnalyzer < RubyLLM::Tool
16
+ description "Analyzes log files for patterns, errors, and performance metrics"
17
+
18
+ def analyze_logs(log_file, time_range = "24h", error_threshold = 10)
19
+ return "Log file not found: #{log_file}" unless File.exist?(log_file)
20
+
21
+ logs = parse_log_file(log_file)
22
+ filtered_logs = filter_by_time(logs, time_range)
23
+
24
+ analysis = {
25
+ total_entries: filtered_logs.length,
26
+ error_count: count_errors(filtered_logs),
27
+ warning_count: count_warnings(filtered_logs),
28
+ top_errors: find_top_errors(filtered_logs, 5),
29
+ performance_stats: calculate_performance_stats(filtered_logs),
30
+ anomalies: detect_anomalies(filtered_logs),
31
+ recommendations: generate_recommendations(filtered_logs, error_threshold)
32
+ }
33
+
34
+ JSON.pretty_generate(analysis)
35
+ end
36
+
37
+ def extract_error_patterns(log_file, pattern_limit = 10)
38
+ return "Log file not found: #{log_file}" unless File.exist?(log_file)
39
+
40
+ errors = []
41
+ File.foreach(log_file) do |line|
42
+ if line.match?(/ERROR|FATAL|EXCEPTION/i)
43
+ errors << extract_error_context(line)
44
+ end
45
+ end
46
+
47
+ patterns = group_similar_errors(errors)
48
+ top_patterns = patterns.sort_by { |_, count| -count }.first(pattern_limit)
49
+
50
+ {
51
+ total_errors: errors.length,
52
+ unique_patterns: patterns.length,
53
+ top_patterns: top_patterns.map { |pattern, count|
54
+ { pattern: pattern, occurrences: count, severity: assess_severity(pattern) }
55
+ }
56
+ }.to_json
57
+ end
58
+
59
+ def performance_report(log_file, metric = "response_time")
60
+ logs = parse_log_file(log_file)
61
+ performance_data = extract_performance_data(logs, metric)
62
+
63
+ return "No performance data found for metric: #{metric}" if performance_data.empty?
64
+
65
+ stats = calculate_detailed_stats(performance_data)
66
+ percentiles = calculate_percentiles(performance_data)
67
+ trends = analyze_trends(performance_data)
68
+
69
+ {
70
+ metric: metric,
71
+ statistics: stats,
72
+ percentiles: percentiles,
73
+ trends: trends,
74
+ alerts: generate_performance_alerts(stats, percentiles)
75
+ }.to_json
76
+ end
77
+
78
+ private
79
+
80
+ def parse_log_file(file_path)
81
+ logs = []
82
+ File.foreach(file_path) do |line|
83
+ parsed = parse_log_line(line.strip)
84
+ logs << parsed if parsed
85
+ end
86
+ logs
87
+ end
88
+
89
+ def parse_log_line(line)
90
+ # Support multiple log formats
91
+ formats = [
92
+ /^(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(?<level>\w+)\] (?<message>.*)/,
93
+ /^(?<level>\w+) (?<timestamp>\w{3} \d{2} \d{2}:\d{2}:\d{2}) (?<message>.*)/,
94
+ /^\[(?<timestamp>.*?)\] (?<level>\w+): (?<message>.*)/
95
+ ]
96
+
97
+ formats.each do |format|
98
+ match = line.match(format)
99
+ if match
100
+ return {
101
+ timestamp: parse_timestamp(match[:timestamp]),
102
+ level: match[:level].upcase,
103
+ message: match[:message],
104
+ raw: line
105
+ }
106
+ end
107
+ end
108
+
109
+ nil # Unable to parse
110
+ end
111
+
112
+ def filter_by_time(logs, time_range)
113
+ cutoff = case time_range
114
+ when /(\d+)h/ then Time.now - ($1.to_i * 3600)
115
+ when /(\d+)d/ then Time.now - ($1.to_i * 86400)
116
+ when /(\d+)m/ then Time.now - ($1.to_i * 60)
117
+ else Time.now - 86400 # Default: 24 hours
118
+ end
119
+
120
+ logs.select { |log| log[:timestamp] && log[:timestamp] > cutoff }
121
+ end
122
+
123
+ def count_errors(logs)
124
+ logs.count { |log| ['ERROR', 'FATAL', 'CRITICAL'].include?(log[:level]) }
125
+ end
126
+
127
+ def count_warnings(logs)
128
+ logs.count { |log| log[:level] == 'WARN' || log[:level] == 'WARNING' }
129
+ end
130
+
131
+ def find_top_errors(logs, limit)
132
+ error_logs = logs.select { |log| ['ERROR', 'FATAL'].include?(log[:level]) }
133
+ error_groups = error_logs.group_by { |log| normalize_error_message(log[:message]) }
134
+
135
+ error_groups.map { |error, occurrences|
136
+ {
137
+ error: error,
138
+ count: occurrences.length,
139
+ first_seen: occurrences.map { |o| o[:timestamp] }.min,
140
+ last_seen: occurrences.map { |o| o[:timestamp] }.max,
141
+ sample: occurrences.first[:raw]
142
+ }
143
+ }.sort_by { |e| -e[:count] }.first(limit)
144
+ end
145
+
146
+ def detect_anomalies(logs)
147
+ anomalies = []
148
+
149
+ # Detect error spikes
150
+ hourly_errors = group_by_hour(logs.select { |l| l[:level] == 'ERROR' })
151
+ avg_errors = hourly_errors.values.sum.to_f / hourly_errors.length
152
+
153
+ hourly_errors.each do |hour, count|
154
+ if count > avg_errors * 3 # 3x average is anomalous
155
+ anomalies << {
156
+ type: 'error_spike',
157
+ hour: hour,
158
+ count: count,
159
+ severity: 'high'
160
+ }
161
+ end
162
+ end
163
+
164
+ # Detect unusual silence periods
165
+ if hourly_errors.values.any? { |count| count == 0 }
166
+ anomalies << {
167
+ type: 'unusual_silence',
168
+ description: 'Periods with zero activity detected',
169
+ severity: 'medium'
170
+ }
171
+ end
172
+
173
+ anomalies
174
+ end
175
+ end
176
+ ```
177
+
178
+ #### Configuration File Manager
179
+ ```ruby
180
+ # ~/.aia/tools/config_manager.rb
181
+ require 'yaml'
182
+ require 'json'
183
+ require 'fileutils'
184
+
185
+ class ConfigManager < RubyLLM::Tool
186
+ description "Manages configuration files across different formats (YAML, JSON, ENV)"
187
+
188
+ def analyze_config(config_file)
189
+ return "Config file not found: #{config_file}" unless File.exist?(config_file)
190
+
191
+ format = detect_format(config_file)
192
+ config_data = load_config(config_file, format)
193
+
194
+ analysis = {
195
+ file: config_file,
196
+ format: format,
197
+ structure: analyze_structure(config_data),
198
+ security: security_analysis(config_data),
199
+ completeness: completeness_check(config_data),
200
+ recommendations: generate_config_recommendations(config_data, format)
201
+ }
202
+
203
+ JSON.pretty_generate(analysis)
204
+ end
205
+
206
+ def validate_config(config_file, schema_file = nil)
207
+ return "Config file not found: #{config_file}" unless File.exist?(config_file)
208
+
209
+ format = detect_format(config_file)
210
+ config_data = load_config(config_file, format)
211
+
212
+ validation_results = {
213
+ syntax_valid: true,
214
+ structure_issues: [],
215
+ security_issues: [],
216
+ recommendations: []
217
+ }
218
+
219
+ # Syntax validation
220
+ begin
221
+ load_config(config_file, format)
222
+ rescue => e
223
+ validation_results[:syntax_valid] = false
224
+ validation_results[:structure_issues] << "Syntax error: #{e.message}"
225
+ end
226
+
227
+ # Security validation
228
+ security_issues = find_security_issues(config_data)
229
+ validation_results[:security_issues] = security_issues
230
+
231
+ # Schema validation if provided
232
+ if schema_file && File.exist?(schema_file)
233
+ schema_validation = validate_against_schema(config_data, schema_file)
234
+ validation_results[:schema_validation] = schema_validation
235
+ end
236
+
237
+ JSON.pretty_generate(validation_results)
238
+ end
239
+
240
+ def merge_configs(base_config, override_config, output_file = nil)
241
+ base_format = detect_format(base_config)
242
+ override_format = detect_format(override_config)
243
+
244
+ base_data = load_config(base_config, base_format)
245
+ override_data = load_config(override_config, override_format)
246
+
247
+ merged_data = deep_merge(base_data, override_data)
248
+
249
+ if output_file
250
+ output_format = detect_format(output_file)
251
+ save_config(merged_data, output_file, output_format)
252
+ "Configuration merged and saved to: #{output_file}"
253
+ else
254
+ JSON.pretty_generate(merged_data)
255
+ end
256
+ end
257
+
258
+ def extract_secrets(config_file, patterns = nil)
259
+ content = File.read(config_file)
260
+
261
+ default_patterns = [
262
+ /password\s*[:=]\s*["']?([^"'\s]+)["']?/i,
263
+ /api[_-]?key\s*[:=]\s*["']?([^"'\s]+)["']?/i,
264
+ /secret\s*[:=]\s*["']?([^"'\s]+)["']?/i,
265
+ /token\s*[:=]\s*["']?([^"'\s]+)["']?/i,
266
+ /database_url\s*[:=]\s*["']?([^"'\s]+)["']?/i
267
+ ]
268
+
269
+ patterns ||= default_patterns
270
+ secrets = []
271
+
272
+ patterns.each do |pattern|
273
+ content.scan(pattern) do |match|
274
+ secrets << {
275
+ type: detect_secret_type(pattern),
276
+ value: mask_secret(match[0]),
277
+ line: content.lines.find_index { |line| line.include?(match[0]) } + 1,
278
+ severity: assess_secret_severity(match[0])
279
+ }
280
+ end
281
+ end
282
+
283
+ {
284
+ file: config_file,
285
+ secrets_found: secrets.length,
286
+ secrets: secrets,
287
+ recommendations: generate_secret_recommendations(secrets)
288
+ }.to_json
289
+ end
290
+
291
+ private
292
+
293
+ def detect_format(file_path)
294
+ ext = File.extname(file_path).downcase
295
+ case ext
296
+ when '.yml', '.yaml' then 'yaml'
297
+ when '.json' then 'json'
298
+ when '.env' then 'env'
299
+ when '.ini' then 'ini'
300
+ else
301
+ # Try to detect from content
302
+ content = File.read(file_path).strip
303
+ return 'json' if content.start_with?('{') || content.start_with?('[')
304
+ return 'yaml' if content.match?(/^\w+:/)
305
+ return 'env' if content.match?(/^\w+=/)
306
+ 'unknown'
307
+ end
308
+ end
309
+
310
+ def load_config(file_path, format)
311
+ content = File.read(file_path)
312
+
313
+ case format
314
+ when 'yaml'
315
+ YAML.safe_load(content)
316
+ when 'json'
317
+ JSON.parse(content)
318
+ when 'env'
319
+ parse_env_file(content)
320
+ else
321
+ { raw_content: content }
322
+ end
323
+ end
324
+
325
+ def parse_env_file(content)
326
+ env_vars = {}
327
+ content.lines.each do |line|
328
+ line = line.strip
329
+ next if line.empty? || line.start_with?('#')
330
+
331
+ key, value = line.split('=', 2)
332
+ env_vars[key] = value&.gsub(/^["']|["']$/, '') if key
333
+ end
334
+ env_vars
335
+ end
336
+
337
+ def deep_merge(base, override)
338
+ base.merge(override) do |key, base_val, override_val|
339
+ if base_val.is_a?(Hash) && override_val.is_a?(Hash)
340
+ deep_merge(base_val, override_val)
341
+ else
342
+ override_val
343
+ end
344
+ end
345
+ end
346
+ end
347
+ ```
348
+
349
+ ### Development Tools
350
+
351
+ #### Code Quality Analyzer
352
+ ```ruby
353
+ # ~/.aia/tools/code_quality.rb
354
+ class CodeQualityAnalyzer < RubyLLM::Tool
355
+ description "Analyzes code quality metrics, complexity, and best practices"
356
+
357
+ def analyze_codebase(directory, language = nil)
358
+ return "Directory not found: #{directory}" unless Dir.exist?(directory)
359
+
360
+ files = find_code_files(directory, language)
361
+ return "No code files found" if files.empty?
362
+
363
+ results = {
364
+ summary: {
365
+ total_files: files.length,
366
+ total_lines: 0,
367
+ languages: {}
368
+ },
369
+ quality_metrics: {},
370
+ issues: [],
371
+ recommendations: []
372
+ }
373
+
374
+ files.each do |file|
375
+ file_analysis = analyze_file(file)
376
+ results[:summary][:total_lines] += file_analysis[:line_count]
377
+
378
+ lang = detect_language(file)
379
+ results[:summary][:languages][lang] ||= 0
380
+ results[:summary][:languages][lang] += 1
381
+
382
+ results[:quality_metrics][file] = file_analysis
383
+ results[:issues].concat(file_analysis[:issues])
384
+ end
385
+
386
+ results[:recommendations] = generate_recommendations(results)
387
+ JSON.pretty_generate(results)
388
+ end
389
+
390
+ def calculate_complexity(file_path)
391
+ return "File not found: #{file_path}" unless File.exist?(file_path)
392
+
393
+ content = File.read(file_path)
394
+ language = detect_language(file_path)
395
+
396
+ complexity = case language
397
+ when 'ruby'
398
+ calculate_ruby_complexity(content)
399
+ when 'python'
400
+ calculate_python_complexity(content)
401
+ when 'javascript'
402
+ calculate_js_complexity(content)
403
+ else
404
+ calculate_generic_complexity(content)
405
+ end
406
+
407
+ {
408
+ file: file_path,
409
+ language: language,
410
+ cyclomatic_complexity: complexity[:cyclomatic],
411
+ cognitive_complexity: complexity[:cognitive],
412
+ maintainability_index: complexity[:maintainability],
413
+ complexity_rating: rate_complexity(complexity[:cyclomatic])
414
+ }.to_json
415
+ end
416
+
417
+ def check_best_practices(file_path)
418
+ return "File not found: #{file_path}" unless File.exist?(file_path)
419
+
420
+ content = File.read(file_path)
421
+ language = detect_language(file_path)
422
+
423
+ violations = []
424
+
425
+ case language
426
+ when 'ruby'
427
+ violations.concat(check_ruby_practices(content))
428
+ when 'python'
429
+ violations.concat(check_python_practices(content))
430
+ when 'javascript'
431
+ violations.concat(check_js_practices(content))
432
+ end
433
+
434
+ # Generic checks
435
+ violations.concat(check_generic_practices(content, file_path))
436
+
437
+ {
438
+ file: file_path,
439
+ language: language,
440
+ violations: violations,
441
+ score: calculate_practice_score(violations),
442
+ recommendations: prioritize_fixes(violations)
443
+ }.to_json
444
+ end
445
+
446
+ private
447
+
448
+ def find_code_files(directory, language = nil)
449
+ extensions = if language
450
+ language_extensions(language)
451
+ else
452
+ %w[.rb .py .js .java .cpp .c .go .rs .php .cs .swift .kt]
453
+ end
454
+
455
+ Dir.glob("#{directory}/**/*").select do |file|
456
+ File.file?(file) && extensions.include?(File.extname(file).downcase)
457
+ end
458
+ end
459
+
460
+ def analyze_file(file_path)
461
+ content = File.read(file_path)
462
+ lines = content.lines
463
+
464
+ {
465
+ file: file_path,
466
+ line_count: lines.length,
467
+ blank_lines: lines.count(&:strip.empty?),
468
+ comment_lines: count_comment_lines(content, detect_language(file_path)),
469
+ complexity: calculate_complexity_metrics(content),
470
+ issues: find_code_issues(content, file_path),
471
+ maintainability: assess_maintainability(content)
472
+ }
473
+ end
474
+
475
+ def calculate_ruby_complexity(content)
476
+ # Simplified Ruby complexity calculation
477
+ cyclomatic = 1 # Base complexity
478
+
479
+ # Add complexity for control structures
480
+ cyclomatic += content.scan(/\b(if|unless|while|until|for|case|rescue)\b/).length
481
+ cyclomatic += content.scan(/&&|\|\|/).length
482
+ cyclomatic += content.scan(/\?.*:/).length # Ternary operators
483
+
484
+ # Method definitions add complexity
485
+ method_count = content.scan(/def\s+\w+/).length
486
+
487
+ {
488
+ cyclomatic: cyclomatic,
489
+ cognitive: calculate_cognitive_complexity(content, 'ruby'),
490
+ maintainability: calculate_maintainability_index(content, cyclomatic),
491
+ method_count: method_count
492
+ }
493
+ end
494
+
495
+ def check_ruby_practices(content)
496
+ violations = []
497
+
498
+ # Check for long methods (>20 lines)
499
+ methods = content.scan(/def\s+\w+.*?end/m)
500
+ methods.each do |method|
501
+ if method.lines.length > 20
502
+ violations << {
503
+ type: 'long_method',
504
+ severity: 'medium',
505
+ message: 'Method exceeds 20 lines',
506
+ line: find_line_number(content, method)
507
+ }
508
+ end
509
+ end
510
+
511
+ # Check for deep nesting
512
+ max_indent = content.lines.map { |line| line.match(/^\s*/)[0].length }.max
513
+ if max_indent > 8
514
+ violations << {
515
+ type: 'deep_nesting',
516
+ severity: 'medium',
517
+ message: 'Excessive nesting detected',
518
+ max_depth: max_indent / 2
519
+ }
520
+ end
521
+
522
+ # Check for missing documentation
523
+ if !content.match?(/^#.*/) && content.match?(/class\s+\w+/)
524
+ violations << {
525
+ type: 'missing_documentation',
526
+ severity: 'low',
527
+ message: 'Class lacks documentation'
528
+ }
529
+ end
530
+
531
+ violations
532
+ end
533
+
534
+ def generate_recommendations(analysis_results)
535
+ recommendations = []
536
+
537
+ # File count recommendations
538
+ if analysis_results[:summary][:total_files] > 100
539
+ recommendations << "Consider organizing large codebase into modules or packages"
540
+ end
541
+
542
+ # Language diversity
543
+ if analysis_results[:summary][:languages].keys.length > 3
544
+ recommendations << "High language diversity may increase maintenance complexity"
545
+ end
546
+
547
+ # Quality-based recommendations
548
+ high_complexity_files = analysis_results[:quality_metrics].select do |file, metrics|
549
+ metrics[:complexity][:cyclomatic] > 10
550
+ end
551
+
552
+ if high_complexity_files.any?
553
+ recommendations << "#{high_complexity_files.length} files have high complexity - consider refactoring"
554
+ end
555
+
556
+ recommendations
557
+ end
558
+ end
559
+ ```
560
+
561
+ ## MCP Integration Examples
562
+
563
+ ### GitHub Repository Analyzer MCP
564
+ ```python
565
+ # github_analyzer_mcp.py
566
+ import asyncio
567
+ import os
568
+ from mcp.server import Server
569
+ from mcp.types import Resource, Tool, TextContent
570
+ import aiohttp
571
+ import json
572
+ from datetime import datetime, timedelta
573
+
574
+ server = Server("github-analyzer", "1.0.0")
575
+
576
+ class GitHubAnalyzer:
577
+ def __init__(self, token):
578
+ self.token = token
579
+ self.headers = {
580
+ 'Authorization': f'token {token}',
581
+ 'Accept': 'application/vnd.github.v3+json'
582
+ }
583
+
584
+ async def analyze_repository(self, owner, repo):
585
+ """Comprehensive repository analysis"""
586
+ async with aiohttp.ClientSession() as session:
587
+ # Get basic repo info
588
+ repo_info = await self._get_repo_info(session, owner, repo)
589
+
590
+ # Get commit activity
591
+ commits = await self._get_commit_activity(session, owner, repo)
592
+
593
+ # Get issues and PRs
594
+ issues = await self._get_issues_analysis(session, owner, repo)
595
+ prs = await self._get_pr_analysis(session, owner, repo)
596
+
597
+ # Get contributors
598
+ contributors = await self._get_contributors_analysis(session, owner, repo)
599
+
600
+ # Get code quality indicators
601
+ code_quality = await self._analyze_code_quality(session, owner, repo)
602
+
603
+ return {
604
+ 'repository': repo_info,
605
+ 'activity': commits,
606
+ 'issues': issues,
607
+ 'pull_requests': prs,
608
+ 'contributors': contributors,
609
+ 'code_quality': code_quality,
610
+ 'health_score': self._calculate_health_score(repo_info, commits, issues, prs),
611
+ 'recommendations': self._generate_recommendations(repo_info, commits, issues, prs)
612
+ }
613
+
614
+ async def _get_repo_info(self, session, owner, repo):
615
+ url = f'https://api.github.com/repos/{owner}/{repo}'
616
+ async with session.get(url, headers=self.headers) as response:
617
+ if response.status == 200:
618
+ data = await response.json()
619
+ return {
620
+ 'name': data['name'],
621
+ 'description': data.get('description', ''),
622
+ 'language': data.get('language', 'Unknown'),
623
+ 'stars': data['stargazers_count'],
624
+ 'forks': data['forks_count'],
625
+ 'open_issues': data['open_issues_count'],
626
+ 'created_at': data['created_at'],
627
+ 'updated_at': data['updated_at'],
628
+ 'size': data['size'],
629
+ 'license': data.get('license', {}).get('name', 'None') if data.get('license') else 'None'
630
+ }
631
+ return {}
632
+
633
+ async def _get_commit_activity(self, session, owner, repo):
634
+ # Get commits from last 30 days
635
+ since = (datetime.now() - timedelta(days=30)).isoformat()
636
+ url = f'https://api.github.com/repos/{owner}/{repo}/commits'
637
+ params = {'since': since, 'per_page': 100}
638
+
639
+ async with session.get(url, headers=self.headers, params=params) as response:
640
+ if response.status == 200:
641
+ commits = await response.json()
642
+
643
+ # Analyze commit patterns
644
+ daily_commits = {}
645
+ authors = {}
646
+
647
+ for commit in commits:
648
+ date = commit['commit']['author']['date'][:10]
649
+ author = commit['commit']['author']['name']
650
+
651
+ daily_commits[date] = daily_commits.get(date, 0) + 1
652
+ authors[author] = authors.get(author, 0) + 1
653
+
654
+ return {
655
+ 'total_commits_30d': len(commits),
656
+ 'daily_average': len(commits) / 30,
657
+ 'most_active_day': max(daily_commits.items(), key=lambda x: x[1]) if daily_commits else None,
658
+ 'active_contributors': len(authors),
659
+ 'top_contributor': max(authors.items(), key=lambda x: x[1]) if authors else None
660
+ }
661
+ return {}
662
+
663
+ def _calculate_health_score(self, repo_info, commits, issues, prs):
664
+ """Calculate overall repository health score (0-100)"""
665
+ score = 0
666
+
667
+ # Activity score (30 points)
668
+ if commits.get('total_commits_30d', 0) > 10:
669
+ score += 30
670
+ elif commits.get('total_commits_30d', 0) > 5:
671
+ score += 20
672
+ elif commits.get('total_commits_30d', 0) > 0:
673
+ score += 10
674
+
675
+ # Documentation score (20 points)
676
+ if repo_info.get('description'):
677
+ score += 10
678
+ # Additional checks would go here (README, wiki, etc.)
679
+
680
+ # Community score (25 points)
681
+ if repo_info.get('stars', 0) > 100:
682
+ score += 15
683
+ elif repo_info.get('stars', 0) > 10:
684
+ score += 10
685
+ elif repo_info.get('stars', 0) > 0:
686
+ score += 5
687
+
688
+ if issues.get('response_time_avg', float('inf')) < 7: # Average response < 7 days
689
+ score += 10
690
+
691
+ # Maintenance score (25 points)
692
+ last_update = datetime.fromisoformat(repo_info.get('updated_at', '1970-01-01T00:00:00Z').replace('Z', '+00:00'))
693
+ days_since_update = (datetime.now(last_update.tzinfo) - last_update).days
694
+
695
+ if days_since_update < 30:
696
+ score += 25
697
+ elif days_since_update < 90:
698
+ score += 15
699
+ elif days_since_update < 365:
700
+ score += 5
701
+
702
+ return min(score, 100)
703
+
704
+ @server.list_tools()
705
+ async def list_tools():
706
+ return [
707
+ Tool(
708
+ name="analyze_repository",
709
+ description="Analyze GitHub repository health, activity, and metrics",
710
+ inputSchema={
711
+ "type": "object",
712
+ "properties": {
713
+ "owner": {"type": "string", "description": "Repository owner"},
714
+ "repo": {"type": "string", "description": "Repository name"}
715
+ },
716
+ "required": ["owner", "repo"]
717
+ }
718
+ ),
719
+ Tool(
720
+ name="compare_repositories",
721
+ description="Compare multiple repositories across key metrics",
722
+ inputSchema={
723
+ "type": "object",
724
+ "properties": {
725
+ "repositories": {
726
+ "type": "array",
727
+ "items": {
728
+ "type": "object",
729
+ "properties": {
730
+ "owner": {"type": "string"},
731
+ "repo": {"type": "string"}
732
+ }
733
+ }
734
+ }
735
+ }
736
+ }
737
+ )
738
+ ]
739
+
740
+ @server.call_tool()
741
+ async def call_tool(name: str, arguments: dict):
742
+ token = os.getenv('GITHUB_TOKEN')
743
+ if not token:
744
+ return TextContent(type="text", text="Error: GITHUB_TOKEN environment variable not set")
745
+
746
+ analyzer = GitHubAnalyzer(token)
747
+
748
+ if name == "analyze_repository":
749
+ result = await analyzer.analyze_repository(arguments["owner"], arguments["repo"])
750
+ return TextContent(type="text", text=json.dumps(result, indent=2))
751
+
752
+ elif name == "compare_repositories":
753
+ comparisons = []
754
+ for repo_data in arguments["repositories"]:
755
+ analysis = await analyzer.analyze_repository(repo_data["owner"], repo_data["repo"])
756
+ comparisons.append({
757
+ "repository": f"{repo_data['owner']}/{repo_data['repo']}",
758
+ "analysis": analysis
759
+ })
760
+
761
+ return TextContent(type="text", text=json.dumps(comparisons, indent=2))
762
+
763
+ return TextContent(type="text", text=f"Unknown tool: {name}")
764
+
765
+ if __name__ == "__main__":
766
+ asyncio.run(server.run())
767
+ ```
768
+
769
+ ### Database Schema Analyzer MCP
770
+ ```python
771
+ # database_schema_mcp.py
772
+ import asyncio
773
+ import os
774
+ from mcp.server import Server
775
+ from mcp.types import Resource, Tool, TextContent
776
+ import asyncpg
777
+ import json
778
+ from datetime import datetime
779
+
780
+ server = Server("database-analyzer", "1.0.0")
781
+
782
+ class DatabaseAnalyzer:
783
+ def __init__(self, connection_string):
784
+ self.connection_string = connection_string
785
+
786
+ async def analyze_schema(self, schema_name='public'):
787
+ """Comprehensive database schema analysis"""
788
+ conn = await asyncpg.connect(self.connection_string)
789
+
790
+ try:
791
+ # Get all tables
792
+ tables = await self._get_tables(conn, schema_name)
793
+
794
+ # Analyze each table
795
+ table_analyses = {}
796
+ for table in tables:
797
+ table_analyses[table['table_name']] = await self._analyze_table(conn, schema_name, table['table_name'])
798
+
799
+ # Get relationships
800
+ relationships = await self._get_relationships(conn, schema_name)
801
+
802
+ # Get indexes
803
+ indexes = await self._get_indexes(conn, schema_name)
804
+
805
+ # Performance analysis
806
+ performance = await self._analyze_performance(conn, schema_name)
807
+
808
+ return {
809
+ 'schema': schema_name,
810
+ 'tables': table_analyses,
811
+ 'relationships': relationships,
812
+ 'indexes': indexes,
813
+ 'performance': performance,
814
+ 'recommendations': self._generate_schema_recommendations(table_analyses, relationships, indexes)
815
+ }
816
+
817
+ finally:
818
+ await conn.close()
819
+
820
+ async def _get_tables(self, conn, schema_name):
821
+ query = """
822
+ SELECT table_name,
823
+ pg_total_relation_size(quote_ident(table_name)) as size_bytes
824
+ FROM information_schema.tables
825
+ WHERE table_schema = $1 AND table_type = 'BASE TABLE'
826
+ ORDER BY table_name
827
+ """
828
+ return await conn.fetch(query, schema_name)
829
+
830
+ async def _analyze_table(self, conn, schema_name, table_name):
831
+ # Get columns
832
+ columns_query = """
833
+ SELECT column_name, data_type, is_nullable, column_default,
834
+ character_maximum_length, numeric_precision, numeric_scale
835
+ FROM information_schema.columns
836
+ WHERE table_schema = $1 AND table_name = $2
837
+ ORDER BY ordinal_position
838
+ """
839
+ columns = await conn.fetch(columns_query, schema_name, table_name)
840
+
841
+ # Get row count
842
+ try:
843
+ row_count = await conn.fetchval(f'SELECT COUNT(*) FROM {schema_name}.{table_name}')
844
+ except:
845
+ row_count = 0
846
+
847
+ # Get primary keys
848
+ pk_query = """
849
+ SELECT column_name
850
+ FROM information_schema.table_constraints tc
851
+ JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name
852
+ WHERE tc.table_schema = $1 AND tc.table_name = $2 AND tc.constraint_type = 'PRIMARY KEY'
853
+ """
854
+ primary_keys = await conn.fetch(pk_query, schema_name, table_name)
855
+
856
+ return {
857
+ 'columns': [dict(col) for col in columns],
858
+ 'row_count': row_count,
859
+ 'primary_keys': [pk['column_name'] for pk in primary_keys],
860
+ 'data_quality': await self._assess_data_quality(conn, schema_name, table_name, columns)
861
+ }
862
+
863
+ async def _assess_data_quality(self, conn, schema_name, table_name, columns):
864
+ quality_issues = []
865
+
866
+ for column in columns:
867
+ col_name = column['column_name']
868
+
869
+ # Check for null values in non-nullable columns
870
+ if column['is_nullable'] == 'NO':
871
+ null_count = await conn.fetchval(
872
+ f'SELECT COUNT(*) FROM {schema_name}.{table_name} WHERE {col_name} IS NULL'
873
+ )
874
+ if null_count > 0:
875
+ quality_issues.append({
876
+ 'type': 'unexpected_nulls',
877
+ 'column': col_name,
878
+ 'count': null_count
879
+ })
880
+
881
+ # Check for duplicate values in potential key columns
882
+ if 'id' in col_name.lower() or col_name.lower().endswith('_key'):
883
+ duplicate_query = f"""
884
+ SELECT COUNT(*) FROM (
885
+ SELECT {col_name}, COUNT(*) as cnt
886
+ FROM {schema_name}.{table_name}
887
+ WHERE {col_name} IS NOT NULL
888
+ GROUP BY {col_name}
889
+ HAVING COUNT(*) > 1
890
+ ) duplicates
891
+ """
892
+ duplicate_count = await conn.fetchval(duplicate_query)
893
+ if duplicate_count > 0:
894
+ quality_issues.append({
895
+ 'type': 'duplicates',
896
+ 'column': col_name,
897
+ 'duplicate_groups': duplicate_count
898
+ })
899
+
900
+ return quality_issues
901
+
902
+ @server.list_tools()
903
+ async def list_tools():
904
+ return [
905
+ Tool(
906
+ name="analyze_schema",
907
+ description="Analyze database schema structure, relationships, and quality",
908
+ inputSchema={
909
+ "type": "object",
910
+ "properties": {
911
+ "schema_name": {"type": "string", "default": "public"}
912
+ }
913
+ }
914
+ ),
915
+ Tool(
916
+ name="performance_analysis",
917
+ description="Analyze database performance metrics and slow queries",
918
+ inputSchema={
919
+ "type": "object",
920
+ "properties": {
921
+ "time_period": {"type": "string", "default": "1h"}
922
+ }
923
+ }
924
+ ),
925
+ Tool(
926
+ name="suggest_indexes",
927
+ description="Suggest database indexes based on query patterns",
928
+ inputSchema={
929
+ "type": "object",
930
+ "properties": {
931
+ "table_name": {"type": "string"},
932
+ "schema_name": {"type": "string", "default": "public"}
933
+ }
934
+ }
935
+ )
936
+ ]
937
+
938
+ @server.call_tool()
939
+ async def call_tool(name: str, arguments: dict):
940
+ connection_string = os.getenv('DATABASE_URL')
941
+ if not connection_string:
942
+ return TextContent(type="text", text="Error: DATABASE_URL environment variable not set")
943
+
944
+ analyzer = DatabaseAnalyzer(connection_string)
945
+
946
+ try:
947
+ if name == "analyze_schema":
948
+ schema_name = arguments.get("schema_name", "public")
949
+ result = await analyzer.analyze_schema(schema_name)
950
+ return TextContent(type="text", text=json.dumps(result, indent=2, default=str))
951
+
952
+ elif name == "performance_analysis":
953
+ # Implementation for performance analysis
954
+ return TextContent(type="text", text="Performance analysis not yet implemented")
955
+
956
+ elif name == "suggest_indexes":
957
+ # Implementation for index suggestions
958
+ return TextContent(type="text", text="Index suggestions not yet implemented")
959
+
960
+ except Exception as e:
961
+ return TextContent(type="text", text=f"Error: {str(e)}")
962
+
963
+ return TextContent(type="text", text=f"Unknown tool: {name}")
964
+
965
+ if __name__ == "__main__":
966
+ asyncio.run(server.run())
967
+ ```
968
+
969
+ ## Integration Workflows
970
+
971
+ ### Full-Stack Application Analysis
972
+ ```markdown
973
+ # ~/.prompts/full_stack_analysis.txt
974
+ //tools file_analyzer.rb,code_quality.rb,config_manager.rb
975
+ //mcp github,filesystem,database
976
+
977
+ # Full-Stack Application Analysis
978
+
979
+ Application: <%= app_name %>
980
+ Repository: <%= repo_url %>
981
+ Environment: <%= environment %>
982
+
983
+ ## Phase 1: Repository Analysis
984
+ Using GitHub MCP client:
985
+ 1. Repository health and activity metrics
986
+ 2. Issue and PR management effectiveness
987
+ 3. Contributor activity and code review patterns
988
+ 4. Release and deployment frequency
989
+
990
+ ## Phase 2: Codebase Quality Assessment
991
+ Using code analysis tools:
992
+ 1. Code quality metrics across all languages
993
+ 2. Complexity analysis and refactoring opportunities
994
+ 3. Security vulnerability scanning
995
+ 4. Test coverage and quality assessment
996
+
997
+ ## Phase 3: Configuration Management
998
+ Using configuration tools:
999
+ 1. Configuration file analysis and security
1000
+ 2. Environment-specific settings validation
1001
+ 3. Secret management assessment
1002
+ 4. Deployment configuration review
1003
+
1004
+ ## Phase 4: Database Architecture
1005
+ Using database MCP client:
1006
+ 1. Schema design and normalization analysis
1007
+ 2. Index optimization opportunities
1008
+ 3. Query performance analysis
1009
+ 4. Data integrity and quality assessment
1010
+
1011
+ ## Phase 5: File System Organization
1012
+ Using filesystem MCP client:
1013
+ 1. Project structure and organization
1014
+ 2. Build and deployment artifacts
1015
+ 3. Documentation completeness
1016
+ 4. Security file analysis
1017
+
1018
+ ## Integration Report
1019
+ Cross-analyze findings to provide:
1020
+ - Overall application health score
1021
+ - Security risk assessment
1022
+ - Performance optimization priorities
1023
+ - Maintenance burden analysis
1024
+ - Deployment readiness checklist
1025
+ - Prioritized improvement recommendations
1026
+
1027
+ Generate comprehensive analysis with actionable insights for each identified area.
1028
+ ```
1029
+
1030
+ ### DevOps Pipeline Assessment
1031
+ ```markdown
1032
+ # ~/.prompts/devops_pipeline_analysis.txt
1033
+ //tools log_analyzer.rb,config_manager.rb
1034
+ //mcp github,filesystem
1035
+
1036
+ # DevOps Pipeline Analysis
1037
+
1038
+ Project: <%= project_name %>
1039
+ Pipeline type: <%= pipeline_type %>
1040
+
1041
+ ## CI/CD Configuration Analysis
1042
+ Using configuration tools:
1043
+ 1. Build configuration validation (GitHub Actions, Jenkins, etc.)
1044
+ 2. Deployment script analysis and security
1045
+ 3. Environment configuration consistency
1046
+ 4. Secret management in CI/CD
1047
+
1048
+ ## Pipeline Performance Analysis
1049
+ Using log analysis tools:
1050
+ 1. Build time trends and optimization opportunities
1051
+ 2. Failure rate analysis and common failure patterns
1052
+ 3. Deployment frequency and success rates
1053
+ 4. Resource utilization during builds
1054
+
1055
+ ## Repository Integration Assessment
1056
+ Using GitHub MCP:
1057
+ 1. Branch protection rules and policies
1058
+ 2. Automated testing integration
1059
+ 3. Code review automation
1060
+ 4. Release management processes
1061
+
1062
+ ## Infrastructure as Code Review
1063
+ Using filesystem MCP:
1064
+ 1. Terraform/CloudFormation template analysis
1065
+ 2. Docker configuration optimization
1066
+ 3. Kubernetes manifest validation
1067
+ 4. Infrastructure security assessment
1068
+
1069
+ ## Recommendations
1070
+ Generate prioritized recommendations for:
1071
+ - Pipeline speed improvements
1072
+ - Security enhancements
1073
+ - Reliability improvements
1074
+ - Cost optimization opportunities
1075
+ - Automation enhancement suggestions
1076
+
1077
+ Provide implementation timeline and impact assessment for each recommendation.
1078
+ ```
1079
+
1080
+ ## Advanced Integration Patterns
1081
+
1082
+ ### Multi-Environment Consistency Checker
1083
+ ```ruby
1084
+ # ~/.aia/tools/environment_checker.rb
1085
+ class EnvironmentChecker < RubyLLM::Tool
1086
+ description "Compares configurations and deployments across multiple environments"
1087
+
1088
+ def compare_environments(environments_config)
1089
+ environments = JSON.parse(environments_config)
1090
+ comparison_results = {}
1091
+
1092
+ environments.each do |env_name, config|
1093
+ comparison_results[env_name] = analyze_environment(env_name, config)
1094
+ end
1095
+
1096
+ # Cross-environment analysis
1097
+ consistency_report = analyze_consistency(comparison_results)
1098
+ drift_analysis = detect_configuration_drift(comparison_results)
1099
+
1100
+ {
1101
+ environments: comparison_results,
1102
+ consistency: consistency_report,
1103
+ drift: drift_analysis,
1104
+ recommendations: generate_consistency_recommendations(consistency_report, drift_analysis)
1105
+ }.to_json
1106
+ end
1107
+
1108
+ def validate_deployment_readiness(environment, checklist_items = nil)
1109
+ default_checklist = [
1110
+ 'configuration_files_present',
1111
+ 'secrets_configured',
1112
+ 'database_migrations_applied',
1113
+ 'dependencies_installed',
1114
+ 'health_checks_passing',
1115
+ 'monitoring_configured',
1116
+ 'backup_procedures_verified'
1117
+ ]
1118
+
1119
+ checklist = checklist_items || default_checklist
1120
+ results = {}
1121
+
1122
+ checklist.each do |item|
1123
+ results[item] = check_deployment_item(environment, item)
1124
+ end
1125
+
1126
+ readiness_score = calculate_readiness_score(results)
1127
+ blocking_issues = identify_blocking_issues(results)
1128
+
1129
+ {
1130
+ environment: environment,
1131
+ readiness_score: readiness_score,
1132
+ checklist_results: results,
1133
+ blocking_issues: blocking_issues,
1134
+ deployment_recommended: blocking_issues.empty? && readiness_score > 80
1135
+ }.to_json
1136
+ end
1137
+
1138
+ private
1139
+
1140
+ def analyze_environment(env_name, config)
1141
+ # Analyze single environment
1142
+ {
1143
+ name: env_name,
1144
+ config_files: find_config_files(config['path']),
1145
+ services: check_services(config['services']),
1146
+ database: check_database_connection(config['database']),
1147
+ monitoring: check_monitoring(config['monitoring']),
1148
+ last_deployment: get_last_deployment_info(env_name)
1149
+ }
1150
+ end
1151
+
1152
+ def analyze_consistency(environments)
1153
+ consistency_issues = []
1154
+
1155
+ # Compare configuration structures
1156
+ config_structures = environments.map { |env, data| data[:config_files] }
1157
+ unless config_structures.all? { |structure| structure.keys.sort == config_structures.first.keys.sort }
1158
+ consistency_issues << "Configuration file structures differ between environments"
1159
+ end
1160
+
1161
+ # Compare service configurations
1162
+ service_configs = environments.map { |env, data| data[:services] }
1163
+ unless service_configs.all? { |config| config.keys.sort == service_configs.first.keys.sort }
1164
+ consistency_issues << "Service configurations differ between environments"
1165
+ end
1166
+
1167
+ {
1168
+ consistent: consistency_issues.empty?,
1169
+ issues: consistency_issues,
1170
+ score: calculate_consistency_score(consistency_issues)
1171
+ }
1172
+ end
1173
+ end
1174
+ ```
1175
+
1176
+ ## Related Documentation
1177
+
1178
+ - [Tools Integration](guides/tools.md) - Detailed tool development guide
1179
+ - [MCP Integration](mcp-integration.md) - MCP client development and usage
1180
+ - [Advanced Prompting](advanced-prompting.md) - Complex integration patterns
1181
+ - [Configuration](configuration.md) - Tool and MCP configuration
1182
+ - [Examples Directory](examples/index.md) - Additional examples and templates
1183
+
1184
+ ---
1185
+
1186
+ These examples demonstrate the power of combining RubyLLM tools with MCP clients to create sophisticated analysis and automation workflows. Use them as templates and inspiration for building your own integrated solutions!