@claude-vector/cli
Version:
CLI for Claude-integrated vector search
1,432 lines (1,207 loc) • 51.1 kB
JavaScript
/**
* 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
};
}
}