Token-efficient code reading protocol using Serena's progressive disclosure pattern (90-95% token savings). Provides mandatory workflow, tool reference, and common patterns for symbol-level code navigation.
Uses progressive disclosure to read code at symbol-level instead of entire files, saving 90-95% tokens. Triggers automatically when exploring files, checking signatures, or editing specific methods—always starting with file overview, then signatures, then selective body reading.
/plugin marketplace add metasaver/claude-marketplace/plugin install core-claude-plugin@metasaver-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Purpose: Achieve 90-95% token savings by using Serena's progressive disclosure instead of reading entire files.
ALWAYS use progressive disclosure instead of reading entire files:
mcp__serena__get_symbols_overview({
relative_path: "src/services/auth.service.ts",
});
Returns: File structure showing all top-level symbols (classes, functions, interfaces) with their signatures.
Use when: You need to understand what's in a file before diving into specific code.
mcp__serena__find_symbol({
name_path_pattern: "AuthService/login",
relative_path: "src/services/auth.service.ts",
include_body: false, // Just the signature
depth: 0,
});
Returns: Method/function signature without implementation details.
Use when: You need to understand the interface (parameters, return type) but not the implementation.
mcp__serena__find_symbol({
name_path_pattern: "AuthService/login",
relative_path: "src/services/auth.service.ts",
include_body: true, // Full implementation
depth: 0,
});
Returns: Complete symbol definition including implementation.
Use when: You actually need to read/edit the implementation.
❌ Traditional Approach (Read entire file):
Read file (2,000 lines) = ~5,000 tokens
✅ Serena Progressive Disclosure:
get_symbols_overview → ~200 tokens (file structure)
find_symbol (no body) → ~50 tokens (signature)
find_symbol (with body) → ~100 tokens (implementation)
─────────────────────────────────────────
Total: ~350 tokens
SAVINGS: 93% (4,650 tokens saved)
Real-world impact:
get_symbols_overview - Get file outline with all top-level symbolslist_dir - List directory contentsfind_file - Find files by patternfind_symbol - Search for symbols by name pathfind_referencing_symbols - Find all references to a symbolsearch_for_pattern - Regex search across codebasereplace_symbol_body - Replace symbol implementation without reading full fileinsert_after_symbol - Add content after symbolinsert_before_symbol - Add content before symbolrename_symbol - Rename with cross-codebase updates// Step 1: Get overview
const overview = await mcp__serena__get_symbols_overview({
relative_path: "src/services/payment.service.ts",
});
// Step 2: Check interesting symbol signatures
const processPaymentSignature = await mcp__serena__find_symbol({
name_path_pattern: "PaymentService/processPayment",
include_body: false, // Just signature
});
// Step 3: Read only what you need
const processPaymentBody = await mcp__serena__find_symbol({
name_path_pattern: "PaymentService/processPayment",
include_body: true, // Full implementation
});
// Result: ~350 tokens vs 5,000 tokens (93% savings)
// Find all references to a function
const references = await mcp__serena__find_referencing_symbols({
name_path: "validateToken",
relative_path: "src/utils/auth.utils.ts",
});
// Returns: List of all places that call validateToken
// With code snippets showing context
// ~500 tokens vs 20,000 tokens (97.5% savings)
// No need to read the entire file!
// Just replace the symbol body directly
await mcp__serena__replace_symbol_body({
name_path: "AuthService/login",
relative_path: "src/services/auth.service.ts",
body: `async login(email: string, password: string): Promise<User> {
// New implementation
const user = await this.userRepository.findByEmail(email);
if (!user) throw new UnauthorizedException();
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) throw new UnauthorizedException();
return user;
}`,
});
// Result: ~100 tokens vs 5,000 tokens (98% savings)
// Insert after existing method without reading full file
await mcp__serena__insert_after_symbol({
name_path: "AuthService/login",
relative_path: "src/services/auth.service.ts",
body: `
async logout(userId: string): Promise<void> {
await this.sessionRepository.deleteByUserId(userId);
this.logger.info('User logged out', { userId });
}`,
});
// Result: ~100 tokens vs 5,000 tokens (98% savings)
Serena uses "name paths" to identify symbols (like file paths for code):
// Find any symbol with this name
"UserService"; // Matches: class UserService, interface UserService, etc.
// Find method inside class
"UserService/createUser"; // Matches: createUser method in UserService
// Find nested class
"AuthModule/AuthService"; // Matches: AuthService inside AuthModule
// Absolute path (exact match from file root)
"/UserService/createUser"; // Must be at file root level
// File: auth.service.ts
export class AuthService {
async login() { ... } // Name path: "AuthService/login"
async logout() { ... } // Name path: "AuthService/logout"
private validateToken() { ... } // Name path: "AuthService/validateToken"
}
export function hashPassword() { ... } // Name path: "hashPassword"
// Substring matching (find all methods starting with "get")
find_symbol({
name_path_pattern: "UserService/get",
substring_matching: true,
});
// Matches: getUserById, getUserByEmail, getUserProfile
// Search by symbol kind (only classes)
find_symbol({
name_path_pattern: "Service",
include_kinds: [5], // 5 = Class
});
// Exclude certain kinds
find_symbol({
name_path_pattern: "User",
exclude_kinds: [13], // 13 = Variable
});
// Common LSP symbol kinds:
1 = File;
2 = Module;
3 = Namespace;
4 = Package;
5 = Class;
6 = Method;
7 = Property;
8 = Field;
9 = Constructor;
10 = Enum;
11 = Interface;
12 = Function;
13 = Variable;
14 = Constant;
Always start with overview
get_symbols_overview(file); // See what's there first
Check signatures before reading bodies
find_symbol(name, (include_body = false)); // Interface only
Read only what you need
find_symbol(specific_symbol, (include_body = true)); // One symbol at a time
Use find_referencing_symbols for impact analysis
find_referencing_symbols(symbol); // Before refactoring
Edit at symbol level when possible
replace_symbol_body(); // Precise changes
Use Serena tools for all code exploration
✅ get_symbols_overview("src/services/huge-file.ts") // ~200 tokens
❌ Read("src/services/huge-file.ts") // 5,000 tokens wasted
Use include_body=false for exploration; true only for implementation
✅ get_symbols_overview() // Efficient overview
✅ find_symbol("*", include_body=false) // Signature only
❌ find_symbol("*", include_body=true) // Wasteful
ALWAYS check overview first before guessing symbol names
✅ get_symbols_overview() → see actual names → find_symbol()
❌ find_symbol("createUser") // Might not exist
Rare cases where reading entire file is acceptable:
Very small files (< 50 lines)
.env.example, .nvmrcNon-code files
README.md, CHANGELOG.mdpackage.json, tsconfig.jsonWhen you need full context
Rule of thumb: If a file has symbols (classes, functions), use Serena. Otherwise, Read is fine.
// Error: Symbol "UserService/createUser" not found
// Solutions:
1. Check spelling: get_symbols_overview(file) to see actual names
2. Use substring matching: find_symbol("create", substring_matching=true)
3. Search pattern: search_for_pattern("createUser")
// Multiple symbols match "User"
// Solutions:
1. Use more specific path: "UserService/User" instead of "User"
2. Use absolute path: "/UserService/User"
3. Filter by kind: include_kinds=[5] for classes only
// Don't know which file has the symbol
// Solutions:
1. Omit relative_path: find_symbol("UserService") // Searches entire codebase
2. Restrict to directory: relative_path="src/services"
3. Use pattern search: search_for_pattern("class UserService")
// 1. Get library docs
const docs = await mcp__Context7__get_library_docs({
context7CompatibleLibraryID: "/prisma/prisma",
});
// 2. Find where it's used in your code
const prismaUsage = await mcp__serena__search_for_pattern({
substring_pattern: "PrismaClient",
});
// 3. Understand implementation
const schema = await mcp__serena__get_symbols_overview({
relative_path: "packages/database/schema.prisma",
});
// 1. Check for prior patterns in Serena memories
const memories = await list_memories();
const authPatterns = memories.filter(
(m) => m.includes("auth") || m.includes("pattern"),
);
for (const pattern of authPatterns) {
const content = await read_memory({ memory_file_name: pattern });
}
// 2. Find existing auth code
const authService = await mcp__serena__find_symbol({
name_path_pattern: "AuthService",
include_body: true,
});
// 3. Store new pattern in memory
write_memory({
memory_file_name: "pattern-jwt-auth.md",
content: `# Pattern: JWT Auth with Refresh Tokens
## Implementation
- Access token: 15min TTL
- Refresh token: 7 days in httpOnly cookie
- Refresh rotation on each use
## Tags
auth, jwt, security`,
});
Remember: Progressive Disclosure
Token Savings: 90-95%
When in doubt: Start with get_symbols_overview and work your way down to specific symbols.