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.
- checksums.yaml +4 -4
- data/.version +1 -1
- data/CHANGELOG.md +66 -2
- data/README.md +133 -4
- data/docs/advanced-prompting.md +721 -0
- data/docs/cli-reference.md +582 -0
- data/docs/configuration.md +347 -0
- data/docs/contributing.md +332 -0
- data/docs/directives-reference.md +490 -0
- data/docs/examples/index.md +277 -0
- data/docs/examples/mcp/index.md +479 -0
- data/docs/examples/prompts/analysis/index.md +78 -0
- data/docs/examples/prompts/automation/index.md +108 -0
- data/docs/examples/prompts/development/index.md +125 -0
- data/docs/examples/prompts/index.md +333 -0
- data/docs/examples/prompts/learning/index.md +127 -0
- data/docs/examples/prompts/writing/index.md +62 -0
- data/docs/examples/tools/index.md +292 -0
- data/docs/faq.md +414 -0
- data/docs/guides/available-models.md +366 -0
- data/docs/guides/basic-usage.md +477 -0
- data/docs/guides/chat.md +474 -0
- data/docs/guides/executable-prompts.md +417 -0
- data/docs/guides/first-prompt.md +454 -0
- data/docs/guides/getting-started.md +455 -0
- data/docs/guides/image-generation.md +507 -0
- data/docs/guides/index.md +46 -0
- data/docs/guides/models.md +507 -0
- data/docs/guides/tools.md +856 -0
- data/docs/index.md +173 -0
- data/docs/installation.md +238 -0
- data/docs/mcp-integration.md +612 -0
- data/docs/prompt_management.md +579 -0
- data/docs/security.md +629 -0
- data/docs/tools-and-mcp-examples.md +1186 -0
- data/docs/workflows-and-pipelines.md +563 -0
- data/examples/tools/mcp/github_mcp_server.json +11 -0
- data/examples/tools/mcp/imcp.json +7 -0
- data/lib/aia/chat_processor_service.rb +19 -3
- data/lib/aia/config/base.rb +224 -0
- data/lib/aia/config/cli_parser.rb +409 -0
- data/lib/aia/config/defaults.rb +88 -0
- data/lib/aia/config/file_loader.rb +131 -0
- data/lib/aia/config/validator.rb +184 -0
- data/lib/aia/config.rb +10 -860
- data/lib/aia/directive_processor.rb +27 -372
- data/lib/aia/directives/configuration.rb +114 -0
- data/lib/aia/directives/execution.rb +37 -0
- data/lib/aia/directives/models.rb +178 -0
- data/lib/aia/directives/registry.rb +120 -0
- data/lib/aia/directives/utility.rb +70 -0
- data/lib/aia/directives/web_and_file.rb +71 -0
- data/lib/aia/prompt_handler.rb +23 -3
- data/lib/aia/ruby_llm_adapter.rb +307 -128
- data/lib/aia/session.rb +27 -14
- data/lib/aia/utility.rb +12 -8
- data/lib/aia.rb +11 -2
- data/lib/extensions/ruby_llm/.irbrc +56 -0
- data/mkdocs.yml +165 -0
- metadata +77 -20
- /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!
|