Files
Christian Gick 0879633faf 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>
2026-01-08 11:05:27 +02:00

122 lines
4.0 KiB
JavaScript

/**
* Grep Compressor - Compress search results while preserving essential matches
*
* Strategies:
* - Group by file
* - Show first N matches per file + count
* - Dedupe similar/adjacent matches
* - Prioritize exact matches
*/
function parseGrepOutput(output) {
const matches = [];
const lines = output.split('\n').filter(l => l.trim());
for (const line of lines) {
// Parse format: file:line:content or file:line-content
const match = line.match(/^(.+?):(\d+)[:-](.*)$/);
if (match) {
matches.push({
file: match[1],
line: parseInt(match[2]),
content: match[3],
});
}
}
return matches;
}
function groupByFile(matches) {
const grouped = new Map();
for (const match of matches) {
const existing = grouped.get(match.file) || [];
existing.push(match);
grouped.set(match.file, existing);
}
return grouped;
}
function dedupeAdjacent(matches, threshold = 3) {
if (matches.length <= 1)
return matches;
const result = [matches[0]];
let skipped = 0;
for (let i = 1; i < matches.length; i++) {
const prev = result[result.length - 1];
const curr = matches[i];
// Skip if within threshold lines of previous match
if (curr.line - prev.line <= threshold) {
skipped++;
continue;
}
result.push(curr);
}
// Add note about skipped adjacent matches
if (skipped > 0 && result.length > 0) {
const last = result[result.length - 1];
result.push({
file: last.file,
line: -1,
content: `[${skipped} adjacent matches omitted]`,
});
}
return result;
}
export function compressGrep(output, options = {}) {
const { maxMatchesPerFile = 3, maxTotalMatches = 20, dedupeAdjacent: shouldDedupe = true, showCounts = true, } = options;
const matches = parseGrepOutput(output);
const originalMatches = matches.length;
if (originalMatches === 0) {
return {
content: 'No matches found.',
originalMatches: 0,
compressedMatches: 0,
filesMatched: 0,
savings: '0%',
};
}
const grouped = groupByFile(matches);
const filesMatched = grouped.size;
const result = [];
let totalShown = 0;
// Sort files by match count (most matches first)
const sortedFiles = Array.from(grouped.entries()).sort((a, b) => b[1].length - a[1].length);
for (const [file, fileMatches] of sortedFiles) {
if (totalShown >= maxTotalMatches) {
const remaining = sortedFiles.length - result.filter(l => l.startsWith('## ')).length;
if (remaining > 0) {
result.push(`\n... [${remaining} more files with matches]`);
}
break;
}
// Dedupe adjacent matches if configured
let processed = shouldDedupe ? dedupeAdjacent(fileMatches) : fileMatches;
// Limit matches per file
const totalInFile = fileMatches.length;
const shown = processed.slice(0, maxMatchesPerFile);
const omitted = totalInFile - shown.length;
result.push(`## ${file}`);
if (showCounts && totalInFile > maxMatchesPerFile) {
result.push(`(${totalInFile} matches, showing ${shown.length})`);
}
for (const match of shown) {
if (match.line === -1) {
result.push(` ${match.content}`);
}
else {
result.push(` ${match.line}: ${match.content.trim()}`);
totalShown++;
}
}
if (omitted > 0) {
result.push(` ... [${omitted} more matches in this file]`);
}
result.push('');
}
const compressedMatches = totalShown;
const savings = ((1 - compressedMatches / originalMatches) * 100).toFixed(1);
return {
content: result.join('\n').trim(),
originalMatches,
compressedMatches,
filesMatched,
savings: `${savings}%`,
};
}