| name | Context Compilation with cclsp |
| description | LSP-powered context extraction using cclsp MCP tools, Solargraph, and Sorbet for type-safe code generation. Trigger keywords: cclsp, LSP, context compilation, interface extraction, vocabulary, Guardian, Sorbet, type checking, find_definition, get_diagnostics, Solargraph, type safety |
| version | 1.0.0 |
Context Compilation with cclsp + Sorbet
This skill provides patterns for LSP-powered context extraction and type-safe code generation using cclsp MCP tools, Solargraph, and Sorbet.
1. Tool Stack Overview
1.1 cclsp MCP Bridge
The cclsp MCP server provides Claude Code access to Language Server Protocol functionality:
| Tool | Purpose | Use Case |
|---|---|---|
mcp__cclsp__find_definition |
Find symbol definitions | Locate where methods/classes are defined |
mcp__cclsp__find_references |
Find all references | Discover symbol usage patterns |
mcp__cclsp__get_diagnostics |
Get errors/warnings | Validate code before execution |
mcp__cclsp__rename_symbol |
Rename symbols | Safe refactoring |
mcp__cclsp__rename_symbol_strict |
Rename at position | Precise symbol renaming |
mcp__cclsp__restart_server |
Restart LSP | Recover from errors |
1.2 Solargraph (Ruby LSP)
Solargraph provides Ruby-specific language intelligence:
- Method completion: Know what methods exist on objects
- Go to definition: Jump to class/method definitions
- Hover documentation: Inline YARD documentation
- Diagnostics: Syntax errors, undefined methods
- Workspace symbols: Search all symbols in project
Configuration (.solargraph.yml):
include:
- "**/*.rb"
exclude:
- spec/**/*
- test/**/*
- vendor/**/*
reporters:
- rubocop
- require_not_found
plugins: []
require_paths: []
domains: []
max_files: 5000
1.3 Sorbet (Type Checker)
Sorbet provides static type checking for Ruby:
- Static analysis: Catch type errors at compile time
- Runtime checking: Verify types during execution
- Gradual typing: Adopt incrementally with
# typed:sigils - IDE integration: LSP support via Sorbet Language Server
Type Sigils:
# typed: ignore # Skip this file
# typed: false # Only syntax errors
# typed: true # Check types
# typed: strict # Require signatures
# typed: strong # No T.untyped
Common Commands:
# Type check entire project
bundle exec srb tc
# Type check specific file
bundle exec srb tc app/models/user.rb
# Initialize Sorbet
bundle exec srb init
# Generate RBI files
bundle exec tapioca init
bundle exec tapioca dsl
bundle exec tapioca gems
1.4 Supporting Tools
| Tool | Purpose | Integration |
|---|---|---|
| parser gem | AST analysis | Deep code structure analysis |
| ripper | Built-in Ruby parser | Fallback parsing, always available |
| YARD | Documentation | Solargraph uses YARD for docs |
| Tapioca | RBI generation | Generate type signatures for gems |
2. cclsp Tool Reference
2.1 find_definition
Find where a symbol is defined:
# Usage Pattern
mcp__cclsp__find_definition(
file_path: "app/services/payment_service.rb",
symbol_name: "PaymentGateway",
symbol_kind: "class" # optional: class, method, module
)
# Returns
{
"definitions": [
{
"file": "app/gateways/payment_gateway.rb",
"line": 5,
"column": 1,
"symbol": "PaymentGateway"
}
]
}
Common symbol_kind values:
class- Class definitionsmodule- Module definitionsmethod- Instance methodsfunction- Module/class methodsvariable- Local/instance variablesconstant- Constants
2.2 find_references
Find all usages of a symbol:
# Usage Pattern
mcp__cclsp__find_references(
file_path: "app/models/user.rb",
symbol_name: "authenticate",
include_declaration: true
)
# Returns
{
"references": [
{
"file": "app/models/user.rb",
"line": 45,
"column": 3,
"context": "def authenticate(password)"
},
{
"file": "app/controllers/sessions_controller.rb",
"line": 12,
"column": 8,
"context": "if user.authenticate(params[:password])"
}
]
}
2.3 get_diagnostics
Get errors and warnings for a file:
# Usage Pattern
mcp__cclsp__get_diagnostics(
file_path: "app/services/order_service.rb"
)
# Returns
{
"diagnostics": [
{
"severity": 1, # 1=Error, 2=Warning, 3=Info, 4=Hint
"message": "Undefined method `foo' for OrderService",
"range": {
"start": {"line": 25, "character": 4},
"end": {"line": 25, "character": 7}
},
"source": "solargraph"
}
]
}
Severity Levels:
1- Error (must fix)2- Warning (should investigate)3- Information (good to know)4- Hint (suggestions)
2.4 rename_symbol
Rename a symbol across the codebase:
# Preview changes (dry_run)
mcp__cclsp__rename_symbol(
file_path: "app/models/user.rb",
symbol_name: "full_name",
new_name: "display_name",
dry_run: true
)
# Apply changes
mcp__cclsp__rename_symbol(
file_path: "app/models/user.rb",
symbol_name: "full_name",
new_name: "display_name"
)
3. Interface Extraction Patterns
3.1 Extract Class Interface
Extract public interface from a class:
# Pattern: Interface Extraction
def extract_interface(class_name, file_path)
# 1. Find class definition
definition = mcp__cclsp__find_definition(
file_path: file_path,
symbol_name: class_name,
symbol_kind: "class"
)
# 2. Read the class file
class_content = Read(definition.file)
# 3. Extract public methods
public_methods = class_content.scan(/^\s*def\s+(\w+)/).flatten
# 4. For each method, find references to understand usage
method_interfaces = public_methods.map do |method|
refs = mcp__cclsp__find_references(
file_path: definition.file,
symbol_name: method
)
{
name: method,
defined_at: "#{definition.file}:#{method_line}",
usage_count: refs.count,
callers: refs.map { |r| r.file }.uniq
}
end
method_interfaces
end
3.2 Extract Dependency Graph
Build a dependency graph for a class:
# Pattern: Dependency Graph
def build_dependency_graph(entry_class, entry_file)
graph = { nodes: [], edges: [] }
visited = Set.new
queue = [[entry_class, entry_file]]
while queue.any?
class_name, file_path = queue.shift
next if visited.include?(class_name)
visited.add(class_name)
# Add node
graph[:nodes] << { name: class_name, file: file_path }
# Find references to other classes
class_content = Read(file_path)
constants = class_content.scan(/([A-Z][A-Za-z0-9]+)/).flatten.uniq
constants.each do |const|
definition = mcp__cclsp__find_definition(
file_path: file_path,
symbol_name: const,
symbol_kind: "class"
)
if definition && !visited.include?(const)
graph[:edges] << { from: class_name, to: const }
queue << [const, definition.file]
end
end
end
graph
end
3.3 Per-Task Interface Extraction
Extract interfaces relevant to a specific task:
# Pattern: Task-Specific Interface Extraction
def compile_task_context(task)
context = {
interfaces: [],
vocabulary: [],
cclsp_enhanced: true
}
# 1. Identify files mentioned in task
target_files = task[:files] || []
# 2. For each file, extract interfaces
target_files.each do |file|
# Get diagnostics first (validates file exists)
diagnostics = mcp__cclsp__get_diagnostics(file_path: file)
# Find all symbols in file
symbols = extract_file_symbols(file)
# For each symbol, get definition and references
symbols.each do |symbol|
definition = mcp__cclsp__find_definition(
file_path: file,
symbol_name: symbol[:name],
symbol_kind: symbol[:kind]
)
references = mcp__cclsp__find_references(
file_path: file,
symbol_name: symbol[:name]
)
context[:interfaces] << {
symbol: symbol[:name],
kind: symbol[:kind],
file: file,
definition: definition,
references: references.count,
signature: extract_signature(definition)
}
end
end
context
end
4. Vocabulary Building Patterns
4.1 Project Vocabulary Extraction
Build a vocabulary of project-specific terms:
# Pattern: Project Vocabulary
def build_project_vocabulary
vocabulary = {
models: [],
services: [],
controllers: [],
patterns: [],
domain_terms: []
}
# 1. Scan models
Dir["app/models/**/*.rb"].each do |file|
content = Read(file)
# Extract class name
if content =~ /class\s+(\w+)/
model_name = $1
vocabulary[:models] << {
name: model_name,
file: file,
associations: content.scan(/(?:has_many|belongs_to|has_one)\s+:(\w+)/).flatten,
scopes: content.scan(/scope\s+:(\w+)/).flatten,
validations: content.scan(/validates\s+:(\w+)/).flatten
}
end
end
# 2. Scan services
Dir["app/services/**/*.rb"].each do |file|
content = Read(file)
if content =~ /class\s+(\w+)/
service_name = $1
vocabulary[:services] << {
name: service_name,
file: file,
public_methods: content.scan(/^\s*def\s+(\w+)/).flatten,
dependencies: extract_dependencies(content)
}
end
end
# 3. Extract domain terms from comments and names
all_files = Dir["app/**/*.rb"]
all_files.each do |file|
content = Read(file)
# Extract from comments
comments = content.scan(/#\s*(.+)$/).flatten
# Extract from class/method names
identifiers = content.scan(/(?:class|def|module)\s+(\w+)/).flatten
# Add unique terms
terms = (comments + identifiers).map(&:downcase).uniq
vocabulary[:domain_terms].concat(terms)
end
vocabulary[:domain_terms].uniq!
vocabulary
end
4.2 Symbol Vocabulary for Generation
Build vocabulary to guide code generation:
# Pattern: Generation Vocabulary
def build_generation_vocabulary(target_file)
vocab = {
available_classes: [],
available_methods: [],
common_patterns: [],
naming_conventions: []
}
# 1. Find all classes in the project
Dir["app/**/*.rb"].each do |file|
content = Read(file)
classes = content.scan(/class\s+(\w+)/).flatten
vocab[:available_classes].concat(classes)
end
# 2. For the target file's directory, find common patterns
dir = File.dirname(target_file)
sibling_files = Dir["#{dir}/*.rb"]
sibling_files.each do |file|
content = Read(file)
# Extract method patterns
methods = content.scan(/def\s+(\w+)/).flatten
vocab[:available_methods].concat(methods)
# Extract common patterns
if content.include?("Result.success")
vocab[:common_patterns] << "Result monad"
end
if content.include?("ApplicationService")
vocab[:common_patterns] << "ApplicationService inheritance"
end
end
vocab[:available_classes].uniq!
vocab[:available_methods].uniq!
vocab[:common_patterns].uniq!
vocab
end
5. Guardian Validation Patterns
5.1 Pre-Generation Validation
Validate before generating code:
# Pattern: Pre-Generation Check
def pre_generation_validate(target_file)
validation = { passed: true, issues: [] }
# 1. Check if cclsp is available
begin
mcp__cclsp__get_diagnostics(file_path: "Gemfile")
rescue
validation[:issues] << "cclsp not available - skipping LSP validation"
return validation
end
# 2. Check existing file for errors
if File.exist?(target_file)
diagnostics = mcp__cclsp__get_diagnostics(file_path: target_file)
errors = diagnostics.select { |d| d[:severity] == 1 }
if errors.any?
validation[:passed] = false
validation[:issues] << "Existing file has #{errors.count} errors - fix first"
end
end
# 3. Check parent class exists
# (would need to parse generation template)
validation
end
5.2 Post-Generation Validation (Guardian)
Validate after generating code:
# Pattern: Guardian Validation
def guardian_validate(file_path)
result = {
passed: true,
errors: [],
warnings: [],
suggestions: []
}
# 1. cclsp diagnostics (Solargraph)
diagnostics = mcp__cclsp__get_diagnostics(file_path: file_path)
diagnostics.each do |d|
case d[:severity]
when 1 # Error
result[:passed] = false
result[:errors] << {
line: d[:range][:start][:line],
message: d[:message],
source: d[:source]
}
when 2 # Warning
result[:warnings] << {
line: d[:range][:start][:line],
message: d[:message]
}
when 3, 4 # Info/Hint
result[:suggestions] << {
line: d[:range][:start][:line],
message: d[:message]
}
end
end
# 2. Sorbet type checking (if available)
sorbet_output = `bundle exec srb tc #{file_path} 2>&1`
sorbet_errors = sorbet_output.lines.select { |l| l.start_with?(file_path) }
sorbet_errors.each do |error|
if error =~ /#{file_path}:(\d+):\s*(.+)/
result[:passed] = false
result[:errors] << {
line: $1.to_i,
message: $2,
source: "sorbet"
}
end
end
result
end
5.3 Generate-Validate-Execute-Verify Cycle
Full implementation cycle with Guardian:
# Pattern: Full Implementation Cycle
def implement_with_guardian(file_path, specification, max_attempts: 3)
attempt = 0
loop do
attempt += 1
puts "Attempt #{attempt}/#{max_attempts}: #{file_path}"
# 1. GENERATE
puts " 1/4 GENERATE: Writing code..."
generate_code(file_path, specification)
# 2. VALIDATE (Guardian)
puts " 2/4 VALIDATE: Running Guardian..."
validation = guardian_validate(file_path)
unless validation[:passed]
puts " Guardian found #{validation[:errors].count} errors"
if attempt >= max_attempts
return { success: false, reason: "Max attempts reached" }
end
# Apply fixes and retry
apply_guardian_fixes(file_path, validation[:errors])
next
end
# 3. EXECUTE
puts " 3/4 EXECUTE: Running tests..."
test_result = run_tests_for_file(file_path)
unless test_result[:passed]
puts " Tests failed: #{test_result[:failures].count} failures"
if attempt >= max_attempts
return { success: false, reason: "Tests failed" }
end
# Analyze failures and retry
analyze_and_fix_tests(file_path, test_result[:failures])
next
end
# 4. VERIFY
puts " 4/4 VERIFY: Final check..."
final_check = final_verification(file_path)
return { success: true, attempts: attempt, verification: final_check }
end
end
def apply_guardian_fixes(file_path, errors)
# Group errors by type
undefined_methods = errors.select { |e| e[:message].include?("Undefined method") }
type_errors = errors.select { |e| e[:source] == "sorbet" }
syntax_errors = errors.select { |e| e[:message].include?("syntax") }
# Apply targeted fixes
if undefined_methods.any?
# Find correct method names using find_references
fix_undefined_methods(file_path, undefined_methods)
end
if type_errors.any?
# Add type signatures or fix type mismatches
fix_type_errors(file_path, type_errors)
end
if syntax_errors.any?
# Fix syntax issues
fix_syntax_errors(file_path, syntax_errors)
end
end
6. Sorbet Integration Patterns
6.1 Type Signature Extraction
Extract Sorbet type signatures from a file:
# Pattern: Sorbet Signature Extraction
def extract_sorbet_signatures(file_path)
content = Read(file_path)
signatures = []
# Find sig blocks
content.scan(/sig\s*\{([^}]+)\}/) do |sig_content|
sig = sig_content[0]
# Parse params
params = {}
sig.scan(/params\(([^)]+)\)/) do |params_str|
params_str[0].split(",").each do |param|
name, type = param.strip.split(":").map(&:strip)
params[name] = type
end
end
# Parse returns
returns = nil
if sig =~ /returns\(([^)]+)\)/
returns = $1.strip
end
signatures << { params: params, returns: returns }
end
signatures
end
6.2 Type-Guided Generation
Use type information to guide code generation:
# Pattern: Type-Guided Generation
def generate_with_types(file_path, method_spec)
# 1. Look up existing type signatures in project
similar_methods = find_similar_methods(method_spec[:name])
# 2. Infer expected types from callers
references = mcp__cclsp__find_references(
file_path: file_path,
symbol_name: method_spec[:name]
)
inferred_types = infer_types_from_usage(references)
# 3. Generate with explicit types
signature = <<~RUBY
sig { params(#{format_params(inferred_types[:params])}).returns(#{inferred_types[:returns]}) }
def #{method_spec[:name]}(#{format_args(method_spec[:args])})
# Implementation
end
RUBY
signature
end
6.3 Sorbet Strictness Levels
Apply appropriate Sorbet strictness:
# Pattern: Sorbet Strictness Selection
def select_sorbet_strictness(file_path)
# New files: start with # typed: true
# Critical business logic: use # typed: strict
# Generated code: use # typed: false initially
case file_path
when /app\/services\//
"# typed: strict" # Services should have strong types
when /app\/models\//
"# typed: true" # Models can start with basic types
when /app\/controllers\//
"# typed: false" # Controllers often have complex types
when /lib\//
"# typed: strict" # Library code should be well-typed
else
"# typed: true" # Default to basic type checking
end
end
7. Graceful Degradation
7.1 Tool Availability Check
Check which tools are available:
# Pattern: Availability Check
check_tool_availability() {
local availability="{}"
# Check cclsp
if mcp__cclsp__get_diagnostics --file_path "Gemfile" 2>/dev/null; then
availability=$(echo "$availability" | jq '.cclsp = true')
else
availability=$(echo "$availability" | jq '.cclsp = false')
fi
# Check Solargraph
if gem list solargraph -i &>/dev/null; then
availability=$(echo "$availability" | jq '.solargraph = true')
else
availability=$(echo "$availability" | jq '.solargraph = false')
fi
# Check Sorbet
if bundle exec srb --version &>/dev/null || gem list sorbet -i &>/dev/null; then
availability=$(echo "$availability" | jq '.sorbet = true')
else
availability=$(echo "$availability" | jq '.sorbet = false')
fi
# Check parser gem
if gem list parser -i &>/dev/null; then
availability=$(echo "$availability" | jq '.parser = true')
else
availability=$(echo "$availability" | jq '.parser = false')
fi
echo "$availability"
}
7.2 Fallback Strategies
Fallback when tools are unavailable:
# Pattern: Graceful Degradation
def compile_context_with_fallback(task)
availability = check_tool_availability
context = {
cclsp_enhanced: false,
interfaces: [],
vocabulary: [],
fallback_used: []
}
# Primary: Use cclsp
if availability[:cclsp]
context[:cclsp_enhanced] = true
context[:interfaces] = extract_interfaces_with_cclsp(task)
else
# Fallback: Use grep and AST parsing
context[:fallback_used] << "grep for interface extraction"
context[:interfaces] = extract_interfaces_with_grep(task)
end
# Primary: Use Sorbet for type info
if availability[:sorbet]
context[:type_info] = extract_type_info_with_sorbet(task)
else
# Fallback: Use YARD comments
context[:fallback_used] << "YARD for type hints"
context[:type_info] = extract_type_info_from_yard(task)
end
# Primary: Use parser gem
if availability[:parser]
context[:ast_analysis] = analyze_with_parser(task)
else
# Fallback: Use ripper (always available)
context[:fallback_used] << "ripper for AST"
context[:ast_analysis] = analyze_with_ripper(task)
end
context
end
def extract_interfaces_with_grep(task)
interfaces = []
task[:files].each do |file|
# Use grep to find method definitions
methods = `grep -n "def " #{file} | head -50`.lines
methods.each do |line|
if line =~ /^(\d+):\s*def\s+(\w+)/
interfaces << {
symbol: $2,
kind: "method",
file: file,
line: $1.to_i
}
end
end
end
interfaces
end
7.3 Partial Feature Mode
Enable features based on available tools:
# Pattern: Feature Flags
def determine_enabled_features
features = {
lsp_diagnostics: false,
type_checking: false,
smart_refactoring: false,
vocabulary_building: true, # Always available
interface_extraction: true # Always available (grep fallback)
}
availability = check_tool_availability
if availability[:cclsp]
features[:lsp_diagnostics] = true
features[:smart_refactoring] = true
end
if availability[:sorbet]
features[:type_checking] = true
end
features
end
8. Working Memory Integration
8.1 Store Compiled Context
Store context for implementation phase:
# Pattern: Store Compiled Context
def store_compiled_context(task_id, context)
memory_entry = {
timestamp: Time.now.utc.iso8601,
agent: "context-compiler",
knowledge_type: "compiled_context",
key: "task.#{task_id}.context",
value: context,
confidence: "verified"
}
# Write to working memory file
File.open(".claude/reactree-memory.jsonl", "a") do |f|
f.puts(memory_entry.to_json)
end
end
8.2 Read Compiled Context
Read context during implementation:
# Pattern: Read Compiled Context
def read_compiled_context(task_id)
memory_file = ".claude/reactree-memory.jsonl"
return nil unless File.exist?(memory_file)
# Read most recent context for task
context = nil
File.readlines(memory_file).reverse_each do |line|
entry = JSON.parse(line)
if entry["key"] == "task.#{task_id}.context"
context = entry["value"]
break
end
end
context
end
9. Quick Reference
Command Cheatsheet
# Solargraph
gem install solargraph
solargraph config # Generate .solargraph.yml
solargraph socket --port 7658 # Start language server
# Sorbet
gem install sorbet sorbet-runtime
bundle exec srb init # Initialize Sorbet
bundle exec srb tc # Type check project
bundle exec srb tc app/models/ # Type check directory
bundle exec srb tc --ignore=sorbet/ # Ignore directory
# Tapioca (RBI generation)
gem install tapioca
bundle exec tapioca init # Initialize
bundle exec tapioca gems # Generate gem RBIs
bundle exec tapioca dsl # Generate DSL RBIs
bundle exec tapioca annotations # Sync annotations
# parser gem
gem install parser
ruby -rparser/current -e 'p Parser::CurrentRuby.parse("def foo; end")'
cclsp MCP Quick Reference
# Find where a method is defined
mcp__cclsp__find_definition(
file_path: "app/models/user.rb",
symbol_name: "authenticate"
)
# Find all usages of a class
mcp__cclsp__find_references(
file_path: "app/services/payment_service.rb",
symbol_name: "PaymentService"
)
# Check file for errors
mcp__cclsp__get_diagnostics(
file_path: "app/services/order_service.rb"
)
# Rename symbol (preview)
mcp__cclsp__rename_symbol(
file_path: "app/models/user.rb",
symbol_name: "old_method",
new_name: "new_method",
dry_run: true
)
Guardian Validation Quick Reference
# Full validation cycle
def validate_file(file_path)
# 1. cclsp diagnostics
diagnostics = mcp__cclsp__get_diagnostics(file_path: file_path)
errors = diagnostics.select { |d| d[:severity] == 1 }
# 2. Sorbet check
sorbet_output = `bundle exec srb tc #{file_path} 2>&1`
sorbet_errors = sorbet_output.lines.count { |l| l.start_with?(file_path) }
# 3. Combined result
{
passed: errors.empty? && sorbet_errors == 0,
lsp_errors: errors.count,
sorbet_errors: sorbet_errors
}
end