feat: Tool Compression MCP server for Phase 8

MCP server providing compressed versions of Read/Grep/Glob:
- compressed_read: removes comments, blanks, collapses imports
- compressed_grep: groups by file, dedupes adjacent matches
- compressed_glob: collapses directories, shows type distribution

Test results: 66.7% compression on sample file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-01-08 11:05:27 +02:00
commit 0879633faf
14 changed files with 1598 additions and 0 deletions

128
dist/compressors/glob.js vendored Normal file
View File

@@ -0,0 +1,128 @@
/**
* Glob Compressor - Compress file listing while preserving useful structure
*
* Strategies:
* - Collapse deep directory paths
* - Group by directory with counts
* - Prioritize recently modified files
* - Show file type distribution
*/
function parseFilePath(path) {
const parts = path.split('/');
const name = parts.pop() || '';
const dir = parts.join('/') || '.';
const ext = name.includes('.') ? name.split('.').pop()?.toLowerCase() || '' : '';
return { path, dir, name, ext };
}
function collapseDirectory(dir, depth) {
const parts = dir.split('/').filter(p => p);
if (parts.length <= depth) {
return dir;
}
// Keep first and last N parts
const keep = Math.floor(depth / 2);
const start = parts.slice(0, keep);
const end = parts.slice(-keep);
return [...start, '...', ...end].join('/');
}
function groupByDirectory(files) {
const grouped = new Map();
for (const file of files) {
const existing = grouped.get(file.dir) || [];
existing.push(file);
grouped.set(file.dir, existing);
}
return grouped;
}
function groupByExtension(files) {
const counts = new Map();
for (const file of files) {
const ext = file.ext || '(no extension)';
counts.set(ext, (counts.get(ext) || 0) + 1);
}
return counts;
}
export function compressGlob(paths, options = {}) {
const { maxFiles = 30, collapseDepth = 4, showCounts = true, groupByExtension: showExtensions = true, } = options;
const originalCount = paths.length;
if (originalCount === 0) {
return {
content: 'No files found.',
originalCount: 0,
compressedCount: 0,
directories: 0,
savings: '0%',
};
}
const files = paths.map(parseFilePath);
const byDir = groupByDirectory(files);
const directories = byDir.size;
const result = [];
// Show extension distribution if configured
if (showExtensions && originalCount > 10) {
const extCounts = groupByExtension(files);
const sorted = Array.from(extCounts.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 5);
result.push('**File types:**');
for (const [ext, count] of sorted) {
result.push(` .${ext}: ${count}`);
}
result.push('');
}
// Sort directories by file count (most files first)
const sortedDirs = Array.from(byDir.entries())
.sort((a, b) => b[1].length - a[1].length);
let totalShown = 0;
let dirsShown = 0;
for (const [dir, dirFiles] of sortedDirs) {
if (totalShown >= maxFiles) {
const remainingDirs = sortedDirs.length - dirsShown;
const remainingFiles = originalCount - totalShown;
if (remainingDirs > 0) {
result.push(`\n... [${remainingFiles} more files in ${remainingDirs} directories]`);
}
break;
}
const collapsedDir = collapseDirectory(dir, collapseDepth);
const fileCount = dirFiles.length;
// For directories with many files, show summary
if (fileCount > 5) {
result.push(`📁 ${collapsedDir}/ (${fileCount} files)`);
// Show first few files
const sample = dirFiles.slice(0, 3);
for (const file of sample) {
result.push(` ${file.name}`);
totalShown++;
}
if (fileCount > 3) {
result.push(` ... [${fileCount - 3} more]`);
}
}
else {
// Show all files for small directories
result.push(`📁 ${collapsedDir}/`);
for (const file of dirFiles) {
result.push(` ${file.name}`);
totalShown++;
if (totalShown >= maxFiles)
break;
}
}
dirsShown++;
result.push('');
}
// Summary line
if (showCounts) {
result.unshift(`**Found ${originalCount} files in ${directories} directories**\n`);
}
const compressedCount = totalShown;
const savings = ((1 - result.length / (originalCount + directories)) * 100).toFixed(1);
return {
content: result.join('\n').trim(),
originalCount,
compressedCount,
directories,
savings: `${savings}%`,
};
}