UNPKG

@claude-vector/cli

Version:

CLI for Claude-integrated vector search

1,432 lines (1,207 loc) 51.1 kB
/** * SmartSearch - 自動プロジェクト検出によるベクトル検索 * 任意のディレクトリから実行可能なCLIコマンド */ import { VectorSearchEngine, AdvancedVectorEngine, createDefaultConfig, FileChangeDetector } from '@claude-vector/core'; import { ContextManager, SessionManager } from '@claude-vector/claude-tools'; import fs from 'fs/promises'; import fsSync from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import dotenv from 'dotenv'; import crypto from 'crypto'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export class SmartClaude { constructor() { // 環境変数を読み込み this.loadEnvironmentVariables(); this.config = createDefaultConfig(); this.searchEngine = new VectorSearchEngine(this.config); this.advancedEngine = null; // AI駆動エンジン(必要時に初期化) this.contextManager = new ContextManager(200000); this.sessionManager = new SessionManager(); this.fileChangeDetector = new FileChangeDetector({ verboseLogging: process.env.DEBUG === 'true' }); this.projectRoot = null; this.isInitialized = false; } /** * 環境変数を読み込み */ loadEnvironmentVariables() { // プロジェクトルートから.envファイルを探す const possibleEnvFiles = [ '.env', '.env.local', '.env.development', '.env.development.local' ]; // 現在のディレクトリから始めて上位ディレクトリを探す let currentDir = process.cwd(); for (let i = 0; i < 10; i++) { for (const envFile of possibleEnvFiles) { const envPath = path.join(currentDir, envFile); try { if (fsSync.existsSync(envPath)) { dotenv.config({ path: envPath }); console.log(`📋 Loaded environment variables from: ${envPath}`); return; } } catch (error) { // ファイルが読み取れない場合は無視 } } // 親ディレクトリに移動 const parentDir = path.dirname(currentDir); if (parentDir === currentDir) break; currentDir = parentDir; } // デフォルトの.envも試す dotenv.config(); } /** * プロジェクトルートを自動検出 */ async findProjectRoot(startPath = process.cwd()) { let currentPath = startPath; // 最大10レベルまで親ディレクトリを遡る for (let i = 0; i < 10; i++) { try { // プロジェクトルートの指標となるファイルを探す const indicators = [ '.claude-code-index', '.claude-vector-index', 'package.json', '.git', 'CLAUDE.md', '.claude-search.config.js', 'node_modules' ]; for (const indicator of indicators) { const indicatorPath = path.join(currentPath, indicator); try { const stats = await fs.stat(indicatorPath); // 見つかった場合、そのディレクトリをプロジェクトルートとする console.log(`📁 Project root detected: ${currentPath}`); console.log(` Found: ${indicator}`); return currentPath; } catch (error) { // ファイルが存在しない場合は次の指標をチェック continue; } } // 親ディレクトリに移動 const parentPath = path.dirname(currentPath); if (parentPath === currentPath) { // ルートディレクトリに到達 break; } currentPath = parentPath; } catch (error) { break; } } // 見つからない場合は現在のディレクトリを使用 console.log(`⚠️ Project root not found, using current directory: ${startPath}`); return startPath; } /** * プロジェクトごとの設定を読み込み */ async loadProjectConfig(projectRoot) { // ccvectorは設定ファイルに依存しない - デフォルト設定のみを返す return { filePatterns: [ 'src/**/*.{ts,tsx,js,jsx}', 'app/**/*.{ts,tsx,js,jsx}', 'components/**/*.{ts,tsx}', 'lib/**/*.{ts,js}', 'hooks/**/*.{ts,tsx}', 'services/**/*.{ts,js}', 'features/**/*.{ts,tsx}', 'contexts/**/*.{ts,tsx}', 'types/**/*.{ts,d.ts}', 'utils/**/*.{ts,js}', 'docs/**/*.md', 'README.md', 'CLAUDE.md' ], excludePatterns: [ 'node_modules/**', '.next/**', 'dist/**', '**/*.test.*', '**/__tests__/**', 'coverage/**', '.git/**' ] }; } /** * 初期化処理 */ async initialize(workingDir = process.cwd()) { if (this.isInitialized) return; try { // 1. プロジェクトルートを検出 this.projectRoot = await this.findProjectRoot(workingDir); // 2. プロジェクト設定を読み込み(ファイルパターンのみ) const projectConfig = await this.loadProjectConfig(this.projectRoot); // 3. インデックスディレクトリを自動検出 const possibleIndexDirs = [ '.claude-code-index', '.claude-vector-index' ]; let indexPath = null; let embeddingsPath = null; let chunksPath = null; for (const dir of possibleIndexDirs) { const testPath = path.join(this.projectRoot, dir); const testEmbeddingsPath = path.join(testPath, 'embeddings.json'); const testChunksPath = path.join(testPath, 'chunks.json'); try { await fs.access(testEmbeddingsPath); await fs.access(testChunksPath); indexPath = testPath; embeddingsPath = testEmbeddingsPath; chunksPath = testChunksPath; console.log(`📁 Found index in: ${dir}`); break; } catch (error) { // 次のディレクトリを試す continue; } } if (!indexPath) { throw new Error('No index found in .claude-code-index or .claude-vector-index'); } console.log(`🔍 Loading index from: ${indexPath}`); // 4. インデックスをロード await this.searchEngine.loadIndex(embeddingsPath, chunksPath); this.isInitialized = true; console.log('✅ Smart Claude initialized successfully'); return { projectRoot: this.projectRoot, indexPath, embeddingsCount: this.searchEngine.getStats().totalChunks }; } catch (error) { console.error('❌ Failed to initialize Smart Claude:', error.message); // インデックスが見つからない場合の対処法を提案 if (error.message.includes('No index found')) { console.log('\n💡 Solutions:'); console.log('1. Create index in current project:'); console.log(' npx ccvector index'); console.log('2. Navigate to a project with existing index'); console.log('3. Check if index exists in:'); console.log(' - .claude-code-index/'); console.log(' - .claude-vector-index/'); } throw error; } } /** * スマート検索実行(AI駆動機能付き) */ async search(query, options = {}) { await this.initialize(); const defaultOptions = { maxResults: 20, searchThreshold: 0.3, includeMetadata: true, ...options }; try { console.log(`🔍 Searching in project: ${this.projectRoot}`); console.log(`📝 Query: "${query}"`); // AI駆動検索を試行(OpenAI APIキーがある場合) if (process.env.OPENAI_API_KEY && options.aiOptimized !== false) { try { return await this.aiSearch(query, defaultOptions); } catch (aiError) { console.log('🔄 AI search failed, falling back to standard search...'); console.log(` Reason: ${aiError.message}`); } } // 標準検索にフォールバック const results = await this.searchEngine.search(query, defaultOptions); if (!results.results || results.results.length === 0) { console.log('📭 No results found'); console.log('💡 Try:'); console.log(' - Using different keywords'); console.log(' - Lowering the threshold with --threshold 0.2'); console.log(' - Checking if the index is up to date'); return results; } console.log(`📊 Found ${results.results.length} results (${results.totalMatches} total matches)`); // 結果を整形して表示 results.results.forEach((result, index) => { const relativeFile = path.relative(this.projectRoot, result.chunk.metadata.file); console.log(`\n${index + 1}. ${relativeFile} (Score: ${result.score.toFixed(3)})`); console.log(` ${result.chunk.content.substring(0, 150)}...`); }); return results; } catch (error) { console.error('❌ Search failed:', error.message); throw error; } } /** * AI駆動検索実行 */ async aiSearch(query, options = {}) { if (!this.advancedEngine) { console.log('🤖 Initializing AI-driven search engine...'); this.advancedEngine = new AdvancedVectorEngine({ aiOptimization: true, semanticChunking: true, phaseAdaptation: true, claudeOptimization: true, verboseLogging: false, openaiApiKey: process.env.OPENAI_API_KEY, // パフォーマンスプロファイリング設定 detailedProfiling: options.profile || false, profilingLevel: options.profileLevel || 'standard', profilingOutputFile: options.profileOutput || './.claude-performance.json' }); await this.advancedEngine.initialize(this.projectRoot); // 既存のインデックスを読み込み // インデックスディレクトリを自動検出 let indexPath = null; const possibleIndexDirs = ['.claude-code-index', '.claude-vector-index']; for (const dir of possibleIndexDirs) { const testPath = path.join(this.projectRoot, dir); try { await fs.access(path.join(testPath, 'embeddings.json')); await fs.access(path.join(testPath, 'chunks.json')); await fs.access(path.join(testPath, 'metadata.json')); indexPath = testPath; break; } catch (error) { continue; } } if (!indexPath) { throw new Error('AI-optimized index not found. Run "ccvector index --force" to create one.'); } try { await this.advancedEngine.loadIndex( path.join(indexPath, 'embeddings.json'), path.join(indexPath, 'chunks.json'), path.join(indexPath, 'metadata.json') ); console.log('🧠 AI-driven search engine ready!'); } catch (error) { // 新形式のインデックスが存在しない場合は標準検索にフォールバック throw new Error('AI-optimized index not found. Run "ccvector index --force" to create one.'); } } console.log('🧠 Performing AI-driven search with phase detection...'); const results = await this.advancedEngine.search(query, options); if (!results.results || results.results.length === 0) { console.log('📭 No results found with AI search'); console.log('💡 Try:'); console.log(' - Using different keywords'); console.log(' - Lowering the threshold with --threshold 0.2'); console.log(' - Creating a new AI-optimized index with "ccvector index --force"'); return results; } // 開発フェーズの表示 if (results.phaseContext && results.phaseContext.phase) { console.log(`🎯 Detected development phase: ${results.phaseContext.phase}`); if (results.phaseContext.confidence) { console.log(` Confidence: ${(results.phaseContext.confidence * 100).toFixed(1)}%`); } } console.log(`📊 Found ${results.results.length} AI-optimized results (${results.totalMatches || results.results.length} total matches)`); console.log(`⚡ Search time: ${results.searchTime}ms`); // AI最適化された結果を表示 results.results.forEach((result, index) => { const relativeFile = path.relative(this.projectRoot, result.chunk.metadata.file); console.log(`\n${index + 1}. ${relativeFile} (Score: ${result.score.toFixed(3)})`); // AI最適化されたメタデータがある場合は表示 if (result.chunk.metadata.aiContext) { const aiContext = result.chunk.metadata.aiContext; if (aiContext.importance) { console.log(` 🎯 Importance: ${aiContext.importance.toFixed(2)}`); } if (aiContext.complexity) { console.log(` 📊 Complexity: ${aiContext.complexity.toFixed(2)}`); } } // Claude最適化フォーマットが利用可能な場合 if (result.claudeFormatted) { console.log(` 🤖 Claude-optimized preview:`); console.log(` ${result.claudeFormatted.substring(0, 200)}...`); } else { console.log(` ${result.chunk.content.substring(0, 150)}...`); } }); return results; } /** * インデックス作成 */ async createIndex(force = false, resume = false) { if (!this.projectRoot) { this.projectRoot = await this.findProjectRoot(); } // ccvectorのデフォルトインデックスディレクトリ const indexPath = path.join(this.projectRoot, '.claude-code-index'); // 強制再作成でない場合は差分更新を試行 if (!force) { try { await fs.access(path.join(indexPath, 'embeddings.json')); // 差分更新の実行 const updateResult = await this.updateIndex(); if (updateResult.updated) { console.log(`✅ Index updated successfully. ${updateResult.stats.totalChanges} changes processed.`); return updateResult; } else { console.log('📋 Index is already up to date.'); return { updated: false, stats: updateResult.stats }; } } catch (error) { // インデックスが存在しないので新規作成を続行 console.log('📝 Creating new index...'); } } // 🚨 重要: --forceオプション時は既存インデックスを完全削除 if (force) { console.log('🧹 Cleaning existing index for complete rebuild...'); try { await fs.rm(indexPath, { recursive: true, force: true }); console.log('✅ Existing index cleaned successfully'); } catch (error) { if (error.code !== 'ENOENT') { console.warn(`⚠️ Warning: Could not clean existing index: ${error.message}`); } } // インデックスディレクトリを再作成 try { await fs.mkdir(indexPath, { recursive: true }); } catch (error) { console.warn(`⚠️ Warning: Could not create index directory: ${error.message}`); } } console.log(`🔨 Creating AI-optimized index for project: ${this.projectRoot}`); console.log('🤖 Using Advanced Vector Engine with AI-driven optimization...'); try { // AI駆動ベクトルエンジンを初期化 const advancedEngine = new AdvancedVectorEngine({ aiOptimization: true, semanticChunking: true, phaseAdaptation: true, claudeOptimization: true, verboseLogging: false, openaiApiKey: process.env.OPENAI_API_KEY, // パフォーマンスプロファイリング設定 detailedProfiling: false, // インデックス作成時は基本的にはfalse profilingLevel: 'standard' }); console.log('🚀 Initializing Advanced Vector Engine...'); await advancedEngine.initialize(this.projectRoot); console.log('📊 Starting AI-driven indexing process...'); const startTime = Date.now(); // プロジェクト全体のインデックスを作成(リジューム対応) if (resume) { console.log('🔄 Attempting to resume previous indexing...'); await advancedEngine.resumableCreateProjectIndex(true); } else { await advancedEngine.indexProject(); } const endTime = Date.now(); const duration = ((endTime - startTime) / 1000).toFixed(2); // 統計情報を取得 const stats = advancedEngine.getStats(); console.log('✅ AI-optimized index created successfully!'); console.log(`📈 Statistics:`); console.log(` - Total chunks: ${stats.totalChunks || 'N/A'}`); console.log(` - Indexed files: ${stats.totalFiles || 'N/A'}`); console.log(` - Processing time: ${duration}s`); console.log(` - Index location: ${indexPath}`); console.log(` - AI features: ✅ Semantic chunking, ✅ Phase adaptation, ✅ Claude optimization`); return { success: true, stats, duration, indexPath }; } catch (error) { console.error('❌ Failed to create AI-optimized index:', error.message); if (error.message.includes('OpenAI API key')) { console.log('\n💡 Solution:'); console.log(' Set your OpenAI API key in .env or .env.local:'); console.log(' OPENAI_API_KEY=sk-your-key-here'); } else if (error.message.includes('No supported files found')) { console.log('\n💡 Solutions:'); console.log(' 1. Make sure you are in a project directory with code files'); console.log(' 2. Check that files are not ignored by .gitignore'); console.log(' 3. Verify file extensions are supported (js, ts, jsx, tsx, py, etc.)'); } throw error; } } /** * インデックス差分更新 */ async updateIndex() { if (!this.projectRoot) { this.projectRoot = await this.findProjectRoot(); } // インデックスディレクトリを自動検出 let indexPath = null; const possibleIndexDirs = ['.claude-code-index', '.claude-vector-index']; for (const dir of possibleIndexDirs) { const testPath = path.join(this.projectRoot, dir); try { await fs.access(path.join(testPath, 'embeddings.json')); await fs.access(path.join(testPath, 'chunks.json')); indexPath = testPath; break; } catch (error) { continue; } } if (!indexPath) { throw new Error('No existing index found to update'); } console.log('🔍 Checking for file changes...'); try { // ファイル変更を検出 const changeResult = await this.fileChangeDetector.detectChanges(this.projectRoot, indexPath); if (!changeResult.requiresUpdate) { console.log('✅ No changes detected. Index is up to date.'); return { updated: false, stats: changeResult.stats }; } // より適切な表示メッセージ const newFilesMsg = changeResult.stats.newFiles > 0 ? `${changeResult.stats.newFiles} files need processing` : "0 new"; const unchangedMsg = changeResult.stats.unchangedFiles > 0 ? ` (${changeResult.stats.unchangedFiles} unchanged)` : ""; console.log(`📊 Changes detected: ${newFilesMsg}, ${changeResult.stats.modifiedFiles} modified, ${changeResult.stats.deletedFiles} deleted${unchangedMsg}`); // AI駆動ベクトルエンジンを初期化 const advancedEngine = new AdvancedVectorEngine({ aiOptimization: true, semanticChunking: true, phaseAdaptation: true, claudeOptimization: true, verboseLogging: false, openaiApiKey: process.env.OPENAI_API_KEY, incrementalIndexing: true, realTimeUpdates: false }); console.log('🚀 Initializing Advanced Vector Engine for update...'); await advancedEngine.initialize(this.projectRoot); console.log('📊 Loading existing index...'); await advancedEngine.loadIndex( path.join(indexPath, 'embeddings.json'), path.join(indexPath, 'chunks.json'), path.join(indexPath, 'metadata.json') ); console.log('🔄 Applying incremental updates...'); const updateStartTime = Date.now(); // 削除されたファイルの処理 if (changeResult.changes.deleted.length > 0) { console.log(`🗑️ Removing ${changeResult.changes.deleted.length} deleted files from index...`); for (const deletedFile of changeResult.changes.deleted) { if (advancedEngine.incrementalIndexer) { await advancedEngine.incrementalIndexer.removeFile(deletedFile.path); } } } // 新規ファイルの処理 const addedFiles = changeResult.changes.added; if (addedFiles.length > 0) { console.log(`📝 Processing ${addedFiles.length} new files...`); let processedFiles = 0; let skippedFiles = 0; let actuallyProcessedFiles = 0; for (const fileInfo of addedFiles) { try { // 早期コンテンツチェック(大きなファイルの最適化) const shouldProcess = await this.shouldProcessFile(fileInfo, advancedEngine); if (shouldProcess) { if (advancedEngine.incrementalIndexer) { await advancedEngine.incrementalIndexer.addFile(fileInfo.path); } actuallyProcessedFiles++; } else { skippedFiles++; console.log(`✅ Skipped: ${path.relative(this.projectRoot, fileInfo.path)} (no changes)`); } processedFiles++; // 進捗表示 if (processedFiles % 10 === 0 || processedFiles === addedFiles.length) { const progress = Math.round((processedFiles / addedFiles.length) * 100); console.log(` Progress: ${processedFiles}/${addedFiles.length} files (${progress}%)`); if (process.env.DEBUG && (skippedFiles > 0 || actuallyProcessedFiles > 0)) { console.log(` 📊 Details: ${actuallyProcessedFiles} processed, ${skippedFiles} skipped`); } } } catch (error) { console.warn(`⚠️ Failed to add file ${fileInfo.path}:`, error.message); processedFiles++; } } console.log(`📊 New files summary: ${actuallyProcessedFiles} processed, ${skippedFiles} skipped, ${addedFiles.length} total`); } // 変更されたファイルの処理 const modifiedFiles = changeResult.changes.modified; if (modifiedFiles.length > 0) { console.log(`📝 Processing ${modifiedFiles.length} modified files...`); let processedFiles = 0; let skippedFiles = 0; let actuallyProcessedFiles = 0; for (const fileInfo of modifiedFiles) { try { // 早期コンテンツチェック(大きなファイルの最適化) const shouldProcess = await this.shouldProcessFile(fileInfo, advancedEngine); if (shouldProcess) { if (advancedEngine.incrementalIndexer) { await advancedEngine.incrementalIndexer.updateFile(fileInfo.path); } actuallyProcessedFiles++; } else { skippedFiles++; console.log(`✅ Skipped: ${path.relative(this.projectRoot, fileInfo.path)} (no changes)`); } processedFiles++; // 進捗表示 if (processedFiles % 10 === 0 || processedFiles === modifiedFiles.length) { const progress = Math.round((processedFiles / modifiedFiles.length) * 100); console.log(` Progress: ${processedFiles}/${modifiedFiles.length} files (${progress}%)`); if (process.env.DEBUG && (skippedFiles > 0 || actuallyProcessedFiles > 0)) { console.log(` 📊 Details: ${actuallyProcessedFiles} updated, ${skippedFiles} skipped`); } } } catch (error) { console.warn(`⚠️ Failed to update file ${fileInfo.path}:`, error.message); processedFiles++; } } console.log(`📊 Modified files summary: ${actuallyProcessedFiles} updated, ${skippedFiles} skipped, ${modifiedFiles.length} total`); } // 🚨 重要:IncrementalIndexerの保存処理を実行 if (advancedEngine.incrementalIndexer) { console.log('💾 Saving updated index...'); await advancedEngine.incrementalIndexer.saveIndex(); } const updateTime = Date.now() - updateStartTime; console.log(`✅ Index updated successfully in ${updateTime}ms`); console.log(`📊 Summary: ${changeResult.stats.newFiles} added, ${changeResult.stats.modifiedFiles} modified, ${changeResult.stats.deletedFiles} removed`); return { updated: true, stats: { ...changeResult.stats, updateTime, totalChanges: changeResult.stats.newFiles + changeResult.stats.modifiedFiles + changeResult.stats.deletedFiles } }; } catch (error) { console.error('❌ Failed to update index:', error.message); // 詳細エラー情報 if (process.env.DEBUG) { console.error('Debug info:', error); } throw error; } } /** * ファイルの早期コンテンツチェック */ async shouldProcessFile(fileInfo, advancedEngine) { try { // 新規ファイルは必ず処理 if (!fileInfo.previousVersion) { return true; } // 大きなファイルは事前にコンテンツチェックを実行 const filePath = fileInfo.path; const fileSize = fileInfo.size || 0; const isLargeFile = fileSize > 50 * 1024; // 50KB以上 if (isLargeFile) { console.log(`🔍 Pre-checking large file: ${path.relative(this.projectRoot, filePath)} (${Math.round(fileSize / 1024)}KB)`); } // 既存チャンクを取得 const existingChunks = advancedEngine.incrementalIndexer.indexData.chunks.filter(chunk => chunk.metadata.file === filePath ); if (existingChunks.length === 0) { return true; // 既存チャンクがない場合は処理 } // ファイルの内容を読み取って、簡単なハッシュチェック const content = await fs.readFile(filePath, 'utf-8'); const newContentHash = crypto.createHash('md5').update(content).digest('hex'); // 既存チャンクの内容をハッシュ化 const existingContent = existingChunks.map(chunk => chunk.content).join(''); const existingContentHash = crypto.createHash('md5').update(existingContent).digest('hex'); const hasChanged = newContentHash !== existingContentHash; if (isLargeFile) { console.log(` ${hasChanged ? '📝 Changed' : '✅ Unchanged'}: ${path.relative(this.projectRoot, filePath)}`); } return hasChanged; } catch (error) { console.warn(`⚠️ Failed to pre-check file ${fileInfo.path}:`, error.message); return true; // エラーが発生した場合は処理を続行 } } /** * インデックス変更チェック(更新なし) */ async checkIndexChanges() { if (!this.projectRoot) { this.projectRoot = await this.findProjectRoot(); } // インデックスディレクトリを自動検出 let indexPath = null; const possibleIndexDirs = ['.claude-code-index', '.claude-vector-index']; for (const dir of possibleIndexDirs) { const testPath = path.join(this.projectRoot, dir); try { await fs.access(path.join(testPath, 'embeddings.json')); await fs.access(path.join(testPath, 'chunks.json')); indexPath = testPath; break; } catch (error) { continue; } } if (!indexPath) { throw new Error('No existing index found to check'); } console.log('🔍 Checking for file changes...'); try { // ファイル変更を検出 const changeResult = await this.fileChangeDetector.detectChanges(this.projectRoot, indexPath); if (changeResult.requiresUpdate) { console.log('📊 Change Summary:'); console.log(` New files: ${changeResult.stats.newFiles}`); console.log(` Modified files: ${changeResult.stats.modifiedFiles}`); console.log(` Deleted files: ${changeResult.stats.deletedFiles}`); console.log(` Unchanged files: ${changeResult.stats.unchangedFiles}`); // 変更されたファイルの詳細表示(最大10件) if (changeResult.changes.added.length > 0) { console.log('\n➕ New files:'); changeResult.changes.added.slice(0, 10).forEach(file => { console.log(` ${path.relative(this.projectRoot, file.path)}`); }); if (changeResult.changes.added.length > 10) { console.log(` ... and ${changeResult.changes.added.length - 10} more files`); } } if (changeResult.changes.modified.length > 0) { console.log('\n📝 Modified files:'); changeResult.changes.modified.slice(0, 10).forEach(file => { console.log(` ${path.relative(this.projectRoot, file.path)}`); }); if (changeResult.changes.modified.length > 10) { console.log(` ... and ${changeResult.changes.modified.length - 10} more files`); } } if (changeResult.changes.deleted.length > 0) { console.log('\n🗑️ Deleted files:'); changeResult.changes.deleted.slice(0, 10).forEach(file => { console.log(` ${path.relative(this.projectRoot, file.path)}`); }); if (changeResult.changes.deleted.length > 10) { console.log(` ... and ${changeResult.changes.deleted.length - 10} more files`); } } } return changeResult; } catch (error) { console.error('❌ Failed to check index changes:', error.message); // 詳細エラー情報 if (process.env.DEBUG) { console.error('Debug info:', error); } throw error; } } /** * プロジェクト情報表示 */ async info() { try { await this.initialize(); const stats = this.searchEngine.getStats(); // 現在ロードされているインデックスのパスを取得 let currentIndexPath = 'Unknown'; const possibleIndexDirs = ['.claude-code-index', '.claude-vector-index']; for (const dir of possibleIndexDirs) { const testPath = path.join(this.projectRoot, dir); try { await fs.access(path.join(testPath, 'embeddings.json')); currentIndexPath = testPath; break; } catch (error) { continue; } } console.log('\n📊 Project Information:'); console.log(` Root: ${this.projectRoot}`); console.log(` Index: ${currentIndexPath}`); console.log(` Chunks: ${stats.totalChunks}`); console.log(` Dimensions: ${stats.embeddingDimensions}`); console.log(` Index Size: ${stats.indexSizeEstimate.embeddings} + ${stats.indexSizeEstimate.chunks}`); return stats; } catch (error) { console.error('❌ Failed to get project info:', error.message); // 初期化に失敗した場合でも、プロジェクト情報は表示 console.log('\n📊 Project Information:'); console.log(` Root: ${this.projectRoot || 'Not detected'}`); console.log(` Status: Index not found or corrupted`); throw error; } } /** * パフォーマンス分析実行 */ async runPerformanceAnalysis(analysisType = 'full', options = {}) { await this.initialize(); console.log('🔍 Starting Performance Analysis...'); console.log(`📊 Analysis Type: ${analysisType}`); const analysisResults = { analysisType, timestamp: new Date().toISOString(), projectRoot: this.projectRoot, results: {} }; try { if (analysisType === 'full' || analysisType === 'index') { console.log('\n📊 Analyzing Index Loading Performance...'); analysisResults.results.indexLoading = await this.analyzeIndexLoadingPerformance(options); } if (analysisType === 'full' || analysisType === 'search') { console.log('\n📊 Analyzing Search Performance...'); analysisResults.results.searchPerformance = await this.analyzeSearchPerformance(options); } if (analysisType === 'full' || analysisType === 'memory') { console.log('\n📊 Analyzing Memory Usage...'); analysisResults.results.memoryUsage = await this.analyzeMemoryUsage(options); } // 結果レポート生成 this.generatePerformanceReport(analysisResults); // 結果ファイル保存 if (options.saveResults !== false) { const outputFile = options.outputFile || './.claude-performance-analysis.json'; await fs.writeFile(outputFile, JSON.stringify(analysisResults, null, 2)); console.log(`\n📄 Analysis results saved to: ${outputFile}`); } return analysisResults; } catch (error) { console.error('❌ Performance analysis failed:', error.message); throw error; } } /** * インデックス読み込み性能分析 */ async analyzeIndexLoadingPerformance(options = {}) { const iterations = options.iterations || 3; const results = []; console.log(` Running ${iterations} iterations...`); for (let i = 0; i < iterations; i++) { // 詳細プロファイリング有効でAdvancedEngineを初期化 const engine = new AdvancedVectorEngine({ aiOptimization: true, semanticChunking: true, phaseAdaptation: true, claudeOptimization: true, verboseLogging: false, openaiApiKey: process.env.OPENAI_API_KEY, detailedProfiling: true, profilingLevel: 'detailed', profilingOutputFile: `./.claude-performance-${i}.json` }); await engine.initialize(this.projectRoot); // インデックスディレクトリを自動検出 let indexPath = null; const possibleIndexDirs = ['.claude-code-index', '.claude-vector-index']; for (const dir of possibleIndexDirs) { const testPath = path.join(this.projectRoot, dir); try { await fs.access(path.join(testPath, 'embeddings.json')); await fs.access(path.join(testPath, 'chunks.json')); indexPath = testPath; break; } catch (error) { continue; } } if (!indexPath) { throw new Error('No existing index found for performance analysis'); } const startTime = Date.now(); const startMemory = process.memoryUsage(); try { const loadResult = await engine.loadIndex( path.join(indexPath, 'embeddings.json'), path.join(indexPath, 'chunks.json'), path.join(indexPath, 'metadata.json') ); const endTime = Date.now(); const endMemory = process.memoryUsage(); results.push({ iteration: i + 1, loadTime: endTime - startTime, embeddingsCount: loadResult.embeddings?.length || 0, chunksCount: loadResult.chunks?.length || 0, memoryDelta: endMemory.heapUsed - startMemory.heapUsed, peakMemory: endMemory.heapUsed, performanceSession: loadResult.performanceSession }); console.log(` Iteration ${i + 1}: ${endTime - startTime}ms`); } catch (error) { console.error(` Iteration ${i + 1} failed:`, error.message); results.push({ iteration: i + 1, error: error.message }); } // メモリ解放のため少し待機 await new Promise(resolve => setTimeout(resolve, 1000)); } // 統計計算 const successfulResults = results.filter(r => !r.error); if (successfulResults.length === 0) { return { error: 'All iterations failed' }; } const stats = { totalIterations: iterations, successfulIterations: successfulResults.length, averageLoadTime: successfulResults.reduce((sum, r) => sum + r.loadTime, 0) / successfulResults.length, minLoadTime: Math.min(...successfulResults.map(r => r.loadTime)), maxLoadTime: Math.max(...successfulResults.map(r => r.loadTime)), averageMemoryDelta: successfulResults.reduce((sum, r) => sum + r.memoryDelta, 0) / successfulResults.length, embeddingsCount: successfulResults[0]?.embeddingsCount || 0, chunksCount: successfulResults[0]?.chunksCount || 0, results }; return stats; } /** * 検索性能分析 */ async analyzeSearchPerformance(options = {}) { const testQueries = options.queries || [ 'user authentication', 'error handling', 'database connection', 'API endpoint', 'component lifecycle' ]; const iterations = options.iterations || 2; const results = []; console.log(` Testing ${testQueries.length} queries with ${iterations} iterations each...`); // AI検索エンジンを初期化(詳細プロファイリング有効) await this.aiSearch('initialization test', { profile: true, profileLevel: 'detailed' }); for (const query of testQueries) { const queryResults = []; for (let i = 0; i < iterations; i++) { const startTime = Date.now(); const startMemory = process.memoryUsage(); try { const searchResult = await this.advancedEngine.search(query, { maxResults: 10, searchThreshold: 0.3 }); const endTime = Date.now(); const endMemory = process.memoryUsage(); queryResults.push({ iteration: i + 1, searchTime: endTime - startTime, resultCount: searchResult.results?.length || 0, memoryDelta: endMemory.heapUsed - startMemory.heapUsed, phaseDetected: searchResult.phaseContext?.phase || 'unknown', performanceSession: searchResult.performanceSession }); } catch (error) { queryResults.push({ iteration: i + 1, error: error.message }); } } // クエリ別統計 const successfulQueries = queryResults.filter(r => !r.error); if (successfulQueries.length > 0) { const queryStats = { query, averageSearchTime: successfulQueries.reduce((sum, r) => sum + r.searchTime, 0) / successfulQueries.length, minSearchTime: Math.min(...successfulQueries.map(r => r.searchTime)), maxSearchTime: Math.max(...successfulQueries.map(r => r.searchTime)), averageResultCount: successfulQueries.reduce((sum, r) => sum + r.resultCount, 0) / successfulQueries.length, results: queryResults }; results.push(queryStats); console.log(` "${query}": ${queryStats.averageSearchTime.toFixed(1)}ms avg`); } } return { totalQueries: testQueries.length, successfulQueries: results.length, overallAverageTime: results.reduce((sum, r) => sum + r.averageSearchTime, 0) / results.length, queryResults: results }; } /** * メモリ使用量分析 */ async analyzeMemoryUsage(options = {}) { const memorySnapshots = []; const testOperations = options.operations || ['loadIndex', 'search', 'complexQuery']; console.log(` Monitoring memory usage for ${testOperations.length} operations...`); // 初期メモリ状態 const baseline = process.memoryUsage(); memorySnapshots.push({ operation: 'baseline', timestamp: Date.now(), memory: baseline }); for (const operation of testOperations) { try { switch (operation) { case 'loadIndex': await this.analyzeIndexLoadingPerformance({ iterations: 1 }); break; case 'search': await this.aiSearch('test query', { profile: false }); break; case 'complexQuery': await this.aiSearch('complex search with multiple components and error handling patterns', { profile: false }); break; } // 操作後のメモリ測定 const afterMemory = process.memoryUsage(); memorySnapshots.push({ operation, timestamp: Date.now(), memory: afterMemory, deltaFromBaseline: { heapUsed: afterMemory.heapUsed - baseline.heapUsed, heapTotal: afterMemory.heapTotal - baseline.heapTotal, external: afterMemory.external - baseline.external, rss: afterMemory.rss - baseline.rss } }); console.log(` ${operation}: +${Math.round((afterMemory.heapUsed - baseline.heapUsed) / 1024 / 1024)}MB`); } catch (error) { console.error(` ${operation} failed:`, error.message); } } // メモリ使用パターン分析 const peakMemory = Math.max(...memorySnapshots.map(s => s.memory.heapUsed)); const totalIncrease = memorySnapshots[memorySnapshots.length - 1].memory.heapUsed - baseline.heapUsed; return { baseline: baseline, peakMemory, totalIncrease, snapshots: memorySnapshots, analysis: { memoryLeakSuspected: totalIncrease > 100 * 1024 * 1024, // 100MB threshold peakMemoryUsage: Math.round(peakMemory / 1024 / 1024) + 'MB', totalMemoryIncrease: Math.round(totalIncrease / 1024 / 1024) + 'MB' } }; } /** * パフォーマンスレポート生成 */ generatePerformanceReport(analysisResults) { console.log('\n🔍 Performance Analysis Report'); console.log('=' .repeat(50)); const { results } = analysisResults; // インデックス読み込み性能 if (results.indexLoading) { const idx = results.indexLoading; console.log('\n📊 Index Loading Performance:'); console.log(` Average Load Time: ${idx.averageLoadTime?.toFixed(1)}ms`); console.log(` Range: ${idx.minLoadTime}ms - ${idx.maxLoadTime}ms`); console.log(` Embeddings: ${idx.embeddingsCount?.toLocaleString()} items`); console.log(` Chunks: ${idx.chunksCount?.toLocaleString()} items`); console.log(` Memory Usage: ${Math.round((idx.averageMemoryDelta || 0) / 1024 / 1024)}MB avg`); // ボトルネック判定 if (idx.averageLoadTime > 1000) { console.log(' ⚠️ Slow loading detected (>1s)'); } else if (idx.averageLoadTime > 500) { console.log(' ⚡ Moderate loading time (>500ms)'); } else { console.log(' ✅ Fast loading (<500ms)'); } } // 検索性能 if (results.searchPerformance) { const search = results.searchPerformance; console.log('\n🔍 Search Performance:'); console.log(` Average Search Time: ${search.overallAverageTime?.toFixed(1)}ms`); console.log(` Successful Queries: ${search.successfulQueries}/${search.totalQueries}`); if (search.overallAverageTime > 1000) { console.log(' ⚠️ Slow search detected (>1s)'); } else if (search.overallAverageTime > 500) { console.log(' ⚡ Moderate search time (>500ms)'); } else { console.log(' ✅ Fast search (<500ms)'); } } // メモリ使用量 if (results.memoryUsage) { const memory = results.memoryUsage; console.log('\n💾 Memory Usage Analysis:'); console.log(` Peak Memory: ${memory.analysis.peakMemoryUsage}`); console.log(` Total Increase: ${memory.analysis.totalMemoryIncrease}`); if (memory.analysis.memoryLeakSuspected) { console.log(' ⚠️ Potential memory leak detected'); } else { console.log(' ✅ Memory usage within normal range'); } } // 最適化提案 console.log('\n💡 Optimization Recommendations:'); const recommendations = this.generateOptimizationRecommendations(results); recommendations.forEach((rec, index) => { console.log(` ${index + 1}. ${rec}`); }); } /** * 最適化提案生成 */ generateOptimizationRecommendations(results) { const recommendations = []; if (results.indexLoading?.averageLoadTime > 1000) { recommendations.push('Consider implementing streaming index loading for large datasets'); recommendations.push('Evaluate index compression or binary format for faster I/O'); } if (results.searchPerformance?.overallAverageTime > 500) { recommendations.push('Implement search result caching for common queries'); recommendations.push('Consider search result pre-computation for popular patterns'); } if (results.memoryUsage?.analysis.memoryLeakSuspected) { recommendations.push('Investigate potential memory leaks in vector processing'); recommendations.push('Implement memory pooling for large vector operations'); } if (recommendations.length === 0) { recommendations.push('Performance is within acceptable ranges'); recommendations.push('Consider monitoring performance over time for degradation detection'); } return recommendations; } /** * リアルタイムメモリ監視開始 */ async startMemoryMonitoring(options = {}) { await this.initialize(); console.log('🔍 Starting Real-time Memory Monitoring...'); if (!this.advancedEngine) { // AI検索エンジンが未初期化の場合は初期化 await this.aiSearch('initialization test', { profile: false }); } const memoryMonitor = this.advancedEngine.memoryMonitor; if (!memoryMonitor) { throw new Error('Memory monitoring is not enabled. Enable it in engine options.'); } // 監視設定のカスタマイズ if (options.alertThresholdMB) { memoryMonitor.options.alertThresholdMB = options.alertThresholdMB; } if (options.monitoringInterval) { memoryMonitor.options.monitoringInterval = options.monitoringInterval; } // イベントリスナー設定 const setupEventListeners = () => { memoryMonitor.on('memory_alert', (alert) => { console.log(`⚠️ Memory Alert: ${alert.type}`); console.log(` Details: ${JSON.stringify(alert.details, null, 2)}`); if (alert.recommendations.length > 0) { console.log(' Recommendations:'); alert.recommendations.forEach(rec => console.log(` - ${rec}`)); } }); memoryMonitor.on('alert_resolved', (resolution) => { console.log(`✅ Alert Resolved: ${resolution.type}`); }); memoryMonitor.on('gc_optimization_performed', () => { console.log('♻️ Garbage collection optimization performed'); }); }; setupEventListeners(); // 監視開始 memoryMonitor.startMonitoring({ command: 'cli_memory_monitoring', projectRoot: this.projectRoot, options }); console.log('✅ Memory monitoring started'); console.log(` Monitoring interval: ${memoryMonitor.options.monitoringInterval}ms`); console.log(` Alert threshold: ${memoryMonitor.options.alertThresholdMB}MB`); console.log(` Memory leak threshold: ${memoryMonitor.options.memoryLeakThresholdMB}MB`); return memoryMonitor; } /** * メモリ監視停止 */ async stopMemoryMonitoring() { if (!this.advancedEngine?.memoryMonitor) { console.log('⚠️ Memory monitoring is not active'); return; } const memoryMonitor = this.advancedEngine.memoryMonitor; if (!memoryMonitor.isMonitoring) { console.log('⚠️ Memory monitoring is not running'); return; } memoryMonitor.stopMonitoring(); console.log('🛑 Memory monitoring stopped'); // 最終レポート生成 try { const reportPath = await memoryMonitor.saveReport(); console.log(`📊 Final memory report saved to: ${reportPath}`); // 簡易統計表示 const stats = memoryMonitor.getStats(); console.log('\n📈 Monitoring Summary:'); console.log(` Duration: ${Math.round(stats.monitoringDuration / 1000)}s`); console.log(` Data points: ${stats.historySize}`); console.log(` Alerts triggered: ${stats.alertsTriggered}`); console.log(` Peak memory: ${Math.round(stats.peakMemoryUsage)}MB`); console.log(` Average memory: ${Math.round(stats.averageMemoryUsage)}MB`); } catch (error) { console.error('❌ Failed to generate final report:', error.message); } } /** * メモリ監視状態確認 */ getMemoryMonitoringStatus() { if (!this.advancedEngine?.memoryMonitor) { return { enabled: false, monitoring: false, message: 'Memory monitoring not available' }; } const memoryMonitor = this.advancedEngine.memoryMonitor; const stats = memoryMonitor.getStats(); const currentMemory = memoryMonitor.getCurrentMemoryUsage(); return { enabled: true, monitoring: stats.isMonitoring, stats, currentMemory: { heapUsedMB: Math.round(currentMemory.heapUsed / 1024 / 1024), heapTotalMB: Math.round(currentMemory.heapTotal / 1024 / 1024), rssMB: Math.round(currentMemory.rss / 1024 / 1024) }, alertState: stats.alertState }; } }