context-optimizer
Version:
Context Optimizer MCP Server v1.6.0 - Advanced MCP Server with AI External Memory Features, Persistent Storage, Differential Indexing, Content Compression, and Prompt Packaging for Master/Worker AI
664 lines (578 loc) • 20.7 kB
JavaScript
/**
* Prompt Packager for ContextOptimizer
* プロンプトパッケージャー - Master/Worker用の整形出力
*
* Features:
* - Context package generation for Master/Worker AI
* - Token-aware content selection
* - Priority-based information ranking
* - Structured output format
*/
import { readFileSync, existsSync } from 'fs';
import { join, relative } from 'path';
import PersistentStorage from '../storage/persistent-storage.js';
import ContentCompressor from '../compression/content-compressor.js';
export class PromptPackager {
constructor(config = {}) {
this.config = {
projectRoot: config.projectRoot || process.cwd(),
maxTokens: config.maxTokens || 8000,
maxFiles: config.maxFiles || 20,
maxHistoryEntries: config.maxHistoryEntries || 10,
priorityWeights: config.priorityWeights || {
core: 1.0,
config: 0.9,
utility: 0.7,
normal: 0.5,
test: 0.3
},
includeTypes: config.includeTypes || ['code', 'documentation', 'history'],
...config
};
this.storage = new PersistentStorage({
storagePath: join(this.config.projectRoot, '.context-optimizer', 'storage'),
...config.storage
});
this.compressor = new ContentCompressor({
tokenThreshold: config.compressionThreshold || 1000,
compressionRatio: config.compressionRatio || 0.7,
...config.compression
});
this.tokenEstimator = this.createTokenEstimator();
}
/**
* Generate context package for Master AI
* Master AI用のコンテキストパッケージを生成
*/
async generateMasterPackage(query, options = {}) {
try {
console.log(`🎯 Generating Master AI package for: "${query}"`);
const packageConfig = {
maxTokens: options.maxTokens || this.config.maxTokens,
includeHistory: options.includeHistory !== false,
includeSnapshots: options.includeSnapshots !== false,
compressionLevel: options.compressionLevel || 'balanced',
...options
};
// Search for relevant files and information
const searchResults = await this.searchRelevantContent(query, packageConfig);
// Generate structured package
const package = {
type: 'master',
query: query,
timestamp: new Date().toISOString(),
summary: await this.generateSummary(query, searchResults),
code_snippets: await this.extractCodeSnippets(searchResults, packageConfig),
related_history: packageConfig.includeHistory ?
await this.getRelevantHistory(query, packageConfig) : [],
snapshots: packageConfig.includeSnapshots ?
await this.getRelevantSnapshots(query, packageConfig) : [],
metadata: {
totalTokens: 0,
filesIncluded: 0,
compressionRatio: 1.0,
generationTime: Date.now()
}
};
// Calculate token count and optimize if needed
package.metadata.totalTokens = this.estimatePackageTokens(package);
if (package.metadata.totalTokens > packageConfig.maxTokens) {
package = await this.optimizePackage(package, packageConfig);
}
console.log(`✅ Master package generated: ${package.metadata.totalTokens} tokens, ${package.metadata.filesIncluded} files`);
return package;
} catch (error) {
console.error('❌ Failed to generate Master package:', error);
throw error;
}
}
/**
* Generate context package for Worker AI
* Worker AI用のコンテキストパッケージを生成
*/
async generateWorkerPackage(taskDescription, options = {}) {
try {
console.log(`🔧 Generating Worker AI package for: "${taskDescription}"`);
const packageConfig = {
maxTokens: options.maxTokens || Math.floor(this.config.maxTokens * 0.8),
focusOnCode: options.focusOnCode !== false,
includeDependencies: options.includeDependencies !== false,
compressionLevel: options.compressionLevel || 'aggressive',
...options
};
// Search for task-specific content
const searchResults = await this.searchRelevantContent(taskDescription, packageConfig);
// Generate structured package
const package = {
type: 'worker',
task: taskDescription,
timestamp: new Date().toISOString(),
summary: await this.generateTaskSummary(taskDescription, searchResults),
code_snippets: await this.extractCodeSnippets(searchResults, packageConfig),
dependencies: packageConfig.includeDependencies ?
await this.extractDependencies(searchResults) : [],
related_files: await this.getRelatedFiles(searchResults, packageConfig),
metadata: {
totalTokens: 0,
filesIncluded: 0,
compressionRatio: 1.0,
generationTime: Date.now()
}
};
// Calculate token count and optimize if needed
package.metadata.totalTokens = this.estimatePackageTokens(package);
if (package.metadata.totalTokens > packageConfig.maxTokens) {
package = await this.optimizePackage(package, packageConfig);
}
console.log(`✅ Worker package generated: ${package.metadata.totalTokens} tokens, ${package.metadata.filesIncluded} files`);
return package;
} catch (error) {
console.error('❌ Failed to generate Worker package:', error);
throw error;
}
}
/**
* Search for content relevant to the query
* クエリに関連するコンテンツを検索
*/
async searchRelevantContent(query, config) {
try {
const results = {
files: [],
history: [],
snapshots: []
};
// Search indexed files
const indexedFiles = Array.from(this.storage.indexData.files.values());
const relevantFiles = this.rankFilesByRelevance(query, indexedFiles);
// Select top files based on priority and token budget
let tokenBudget = config.maxTokens * 0.6; // Reserve 60% for files
for (const file of relevantFiles) {
if (tokenBudget <= 0 || results.files.length >= config.maxFiles) break;
const fileTokens = file.tokens || 0;
if (fileTokens <= tokenBudget) {
results.files.push(file);
tokenBudget -= fileTokens;
}
}
return results;
} catch (error) {
console.error('❌ Failed to search relevant content:', error);
return { files: [], history: [], snapshots: [] };
}
}
/**
* Rank files by relevance to query
* クエリとの関連性でファイルをランク付け
*/
rankFilesByRelevance(query, files) {
const queryWords = query.toLowerCase().split(/\s+/);
return files
.map(file => {
let score = 0;
// Path relevance
const path = file.path.toLowerCase();
queryWords.forEach(word => {
if (path.includes(word)) score += 2;
});
// Tag relevance
if (file.tags) {
file.tags.forEach(tag => {
if (queryWords.some(word => tag.toLowerCase().includes(word))) {
score += 1;
}
});
}
// Importance weight
const importance = file.importance || 'normal';
const weight = this.config.priorityWeights[importance] || 0.5;
score *= weight;
return { ...file, relevanceScore: score };
})
.sort((a, b) => b.relevanceScore - a.relevanceScore);
}
/**
* Generate summary for Master AI
* Master AI用の要約を生成
*/
async generateSummary(query, searchResults) {
try {
const summary = {
query: query,
project_overview: await this.getProjectOverview(),
relevant_files: searchResults.files.slice(0, 5).map(file => ({
path: relative(this.config.projectRoot, file.path),
importance: file.importance,
tokens: file.tokens,
summary: file.summary || 'No summary available'
})),
key_insights: await this.extractKeyInsights(query, searchResults),
recommendations: await this.generateRecommendations(query, searchResults)
};
return summary;
} catch (error) {
console.error('❌ Failed to generate summary:', error);
return { query, error: error.message };
}
}
/**
* Generate task summary for Worker AI
* Worker AI用のタスク要約を生成
*/
async generateTaskSummary(taskDescription, searchResults) {
try {
const summary = {
task: taskDescription,
relevant_code_files: searchResults.files
.filter(file => ['core', 'utility'].includes(file.importance))
.slice(0, 3)
.map(file => ({
path: relative(this.config.projectRoot, file.path),
importance: file.importance,
functions: file.astInfo?.functions?.length || 0,
classes: file.astInfo?.classes?.length || 0
})),
dependencies: await this.extractDependencies(searchResults),
implementation_hints: await this.generateImplementationHints(taskDescription, searchResults)
};
return summary;
} catch (error) {
console.error('❌ Failed to generate task summary:', error);
return { task: taskDescription, error: error.message };
}
}
/**
* Extract code snippets from search results
* 検索結果からコードスニペットを抽出
*/
async extractCodeSnippets(searchResults, config) {
try {
const snippets = [];
for (const file of searchResults.files.slice(0, config.maxFiles || 10)) {
try {
if (!existsSync(file.path)) continue;
const content = readFileSync(file.path, 'utf8');
const compressed = this.compressor.compressContent(content, file);
snippets.push({
path: relative(this.config.projectRoot, file.path),
importance: file.importance,
tokens: compressed.tokens,
content: compressed.compressedContent,
summary: compressed.summary,
compressed: compressed.compressed,
compressionRatio: compressed.compressionRatio
});
} catch (error) {
console.warn(`⚠️ Failed to extract snippet from ${file.path}:`, error.message);
}
}
return snippets;
} catch (error) {
console.error('❌ Failed to extract code snippets:', error);
return [];
}
}
/**
* Get relevant history entries
* 関連する履歴エントリを取得
*/
async getRelevantHistory(query, config) {
try {
const historyEntries = this.storage.historyData.entries
.slice(0, config.maxHistoryEntries || 10)
.map(entry => ({
id: entry.id,
timestamp: entry.timestamp,
contextSize: entry.contextSize,
compressed: entry.compressed,
summary: entry.summary,
tags: entry.tags
}));
return historyEntries;
} catch (error) {
console.error('❌ Failed to get relevant history:', error);
return [];
}
}
/**
* Get relevant snapshots
* 関連するスナップショットを取得
*/
async getRelevantSnapshots(query, config) {
try {
const snapshots = this.storage.snapshotsData.snapshots
.slice(0, 3)
.map(snapshot => ({
id: snapshot.id,
timestamp: snapshot.timestamp,
description: snapshot.description,
stats: snapshot.stats
}));
return snapshots;
} catch (error) {
console.error('❌ Failed to get relevant snapshots:', error);
return [];
}
}
/**
* Extract dependencies from search results
* 検索結果から依存関係を抽出
*/
async extractDependencies(searchResults) {
try {
const dependencies = new Set();
for (const file of searchResults.files) {
if (file.astInfo?.imports) {
file.astInfo.imports.forEach(imp => {
dependencies.add(imp.source);
});
}
}
return Array.from(dependencies).slice(0, 10);
} catch (error) {
console.error('❌ Failed to extract dependencies:', error);
return [];
}
}
/**
* Get related files
* 関連ファイルを取得
*/
async getRelatedFiles(searchResults, config) {
try {
return searchResults.files
.slice(0, config.maxFiles || 10)
.map(file => ({
path: relative(this.config.projectRoot, file.path),
importance: file.importance,
size: file.size,
tokens: file.tokens,
tags: file.tags
}));
} catch (error) {
console.error('❌ Failed to get related files:', error);
return [];
}
}
/**
* Get project overview
* プロジェクト概要を取得
*/
async getProjectOverview() {
try {
const stats = this.storage.getStorageStats();
const files = Array.from(this.storage.indexData.files.values());
const overview = {
totalFiles: stats.index.totalFiles,
languages: this.extractLanguages(files),
frameworks: this.extractFrameworks(files),
lastUpdate: stats.index.lastUpdate,
compressionStats: stats.history.compressionStats
};
return overview;
} catch (error) {
console.error('❌ Failed to get project overview:', error);
return { error: error.message };
}
}
/**
* Extract languages from files
* ファイルから言語を抽出
*/
extractLanguages(files) {
const languages = new Map();
files.forEach(file => {
if (file.tags) {
file.tags.forEach(tag => {
if (['typescript', 'javascript', 'python', 'java', 'golang', 'rust'].includes(tag)) {
languages.set(tag, (languages.get(tag) || 0) + 1);
}
});
}
});
return Array.from(languages.entries()).map(([lang, count]) => ({ language: lang, files: count }));
}
/**
* Extract frameworks from files
* ファイルからフレームワークを抽出
*/
extractFrameworks(files) {
const frameworks = new Set();
files.forEach(file => {
if (file.tags) {
file.tags.forEach(tag => {
if (['react', 'vue', 'angular', 'express', 'nextjs'].includes(tag)) {
frameworks.add(tag);
}
});
}
});
return Array.from(frameworks);
}
/**
* Extract key insights
* 重要な洞察を抽出
*/
async extractKeyInsights(query, searchResults) {
try {
const insights = [];
// Analyze file patterns
const coreFiles = searchResults.files.filter(f => f.importance === 'core');
if (coreFiles.length > 0) {
insights.push(`Found ${coreFiles.length} core files that are critical to the project structure`);
}
// Analyze compression stats
const compressionStats = this.storage.getCompressionStats();
if (compressionStats.totalCompressed > 0) {
insights.push(`Project has ${compressionStats.totalCompressed} compressed files with ${(compressionStats.averageCompressionRatio * 100).toFixed(1)}% average compression`);
}
return insights;
} catch (error) {
console.error('❌ Failed to extract key insights:', error);
return [];
}
}
/**
* Generate recommendations
* 推奨事項を生成
*/
async generateRecommendations(query, searchResults) {
try {
const recommendations = [];
// File organization recommendations
const testFiles = searchResults.files.filter(f => f.importance === 'test');
if (testFiles.length === 0) {
recommendations.push('Consider adding test files to improve code quality');
}
// Documentation recommendations
const docFiles = searchResults.files.filter(f => f.tags?.includes('documentation'));
if (docFiles.length === 0) {
recommendations.push('Consider adding documentation files (README.md, docs/)');
}
return recommendations;
} catch (error) {
console.error('❌ Failed to generate recommendations:', error);
return [];
}
}
/**
* Generate implementation hints
* 実装のヒントを生成
*/
async generateImplementationHints(taskDescription, searchResults) {
try {
const hints = [];
// Analyze existing patterns
const coreFiles = searchResults.files.filter(f => f.importance === 'core');
if (coreFiles.length > 0) {
hints.push('Follow existing patterns in core files');
}
// Check for similar functionality
const utilityFiles = searchResults.files.filter(f => f.importance === 'utility');
if (utilityFiles.length > 0) {
hints.push('Reuse utility functions where possible');
}
return hints;
} catch (error) {
console.error('❌ Failed to generate implementation hints:', error);
return [];
}
}
/**
* Optimize package to fit token limits
* トークン制限に合わせてパッケージを最適化
*/
async optimizePackage(package, config) {
try {
console.log(`🔧 Optimizing package: ${package.metadata.totalTokens} -> ${config.maxTokens} tokens`);
// Sort code snippets by importance and token count
package.code_snippets.sort((a, b) => {
const aImportance = this.config.priorityWeights[a.importance] || 0.5;
const bImportance = this.config.priorityWeights[b.importance] || 0.5;
return bImportance - aImportance;
});
// Remove less important snippets
let currentTokens = this.estimatePackageTokens(package);
while (currentTokens > config.maxTokens && package.code_snippets.length > 1) {
const removed = package.code_snippets.pop();
currentTokens -= removed.tokens;
}
// Compress remaining snippets if still over limit
if (currentTokens > config.maxTokens) {
for (const snippet of package.code_snippets) {
if (currentTokens <= config.maxTokens) break;
const compressed = this.compressor.compressContent(snippet.content, {
importance: snippet.importance
});
if (compressed.compressed) {
snippet.content = compressed.compressedContent;
snippet.compressed = true;
snippet.compressionRatio = compressed.compressionRatio;
currentTokens = this.estimatePackageTokens(package);
}
}
}
package.metadata.totalTokens = currentTokens;
package.metadata.compressionRatio = currentTokens / this.estimatePackageTokens(package);
return package;
} catch (error) {
console.error('❌ Failed to optimize package:', error);
return package;
}
}
/**
* Estimate total tokens in package
* パッケージの総トークン数を推定
*/
estimatePackageTokens(package) {
let tokens = 0;
// Estimate tokens for each component
if (package.summary) {
tokens += this.tokenEstimator(package.summary);
}
if (package.code_snippets) {
package.code_snippets.forEach(snippet => {
tokens += snippet.tokens || this.tokenEstimator(snippet.content);
});
}
if (package.related_history) {
package.related_history.forEach(entry => {
tokens += this.tokenEstimator(entry.summary || '');
});
}
if (package.snapshots) {
package.snapshots.forEach(snapshot => {
tokens += this.tokenEstimator(snapshot.description || '');
});
}
return tokens;
}
/**
* Create token estimator function
* トークン推定関数を作成
*/
createTokenEstimator() {
return (text) => {
if (typeof text === 'string') {
return Math.ceil(text.length / 4); // ~4 characters per token
} else if (typeof text === 'object') {
return Math.ceil(JSON.stringify(text).length / 4);
}
return 0;
};
}
/**
* Get packaging statistics
* パッケージング統計を取得
*/
getPackagingStats() {
return {
maxTokens: this.config.maxTokens,
maxFiles: this.config.maxFiles,
priorityWeights: this.config.priorityWeights,
includeTypes: this.config.includeTypes,
storageStats: this.storage.getStorageStats()
};
}
}
export default PromptPackager;