import * as vscode from 'vscode'; import { readWorkspaceFiles } from '../scanner/fileReader'; import { scanWithGemini } from '../scanner/geminiClient'; import { scanWithClaude } from '../scanner/claudeClient'; import { issuesToDiagnostics } from '../ui/diagnostics'; import { ResultsPanel } from '../ui/resultsPanel'; import { ScanIssue } from '../scanner/prompt'; export async function scanWorkspace( context: vscode.ExtensionContext, collection: vscode.DiagnosticCollection, ): Promise { const folders = vscode.workspace.workspaceFolders; if (!folders || folders.length === 0) { vscode.window.showWarningMessage('Guard: ワークスペースが開かれていません'); return; } const geminiKey = await context.secrets.get('guard.geminiKey'); if (!geminiKey) { const action = await vscode.window.showErrorMessage( 'Guard: Gemini API キーが未設定です', 'APIキーを設定', ); if (action === 'APIキーを設定') { await vscode.commands.executeCommand('guard.setApiKeys'); } return; } const config = vscode.workspace.getConfiguration('guard'); const maxFiles: number = config.get('maxFiles') ?? 80; const model: string = config.get('model') ?? 'gemini'; await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: 'Guard: スキャン中...', cancellable: false, }, async progress => { progress.report({ message: 'ファイルを読み込んでいます...' }); const files = readWorkspaceFiles(maxFiles); if (files.length === 0) { vscode.window.showInformationMessage('Guard: スキャン対象のファイルが見つかりませんでした'); return; } const sourceName = folders[0].name; let issues: ScanIssue[] = []; try { progress.report({ message: `Gemini でスキャン中... (${files.length} ファイル)` }); issues = await scanWithGemini(files, geminiKey); } catch (err) { vscode.window.showErrorMessage(`Guard: Gemini スキャンに失敗しました: ${String(err)}`); return; } if (model === 'claude' || model === 'both') { const claudeKey = await context.secrets.get('guard.claudeKey'); if (claudeKey) { try { progress.report({ message: 'Claude でスキャン中...' }); const claudeIssues = await scanWithClaude(files, claudeKey); // Merge: dedup by file + title const seen = new Set(issues.map(i => `${i.file}::${i.title}`)); for (const ci of claudeIssues) { if (!seen.has(`${ci.file}::${ci.title}`)) { issues.push(ci); seen.add(`${ci.file}::${ci.title}`); } } } catch { // Claude is optional — silent fail } } } issuesToDiagnostics(issues, collection); ResultsPanel.show(context, issues, sourceName); const danger = issues.filter(i => i.severity === 'danger').length; const warning = issues.filter(i => i.severity === 'warning').length; const msg = `Guard: ${issues.length} 件検出 (危険 ${danger} / 警告 ${warning})`; if (danger > 0) { vscode.window.showErrorMessage(msg); } else if (warning > 0) { vscode.window.showWarningMessage(msg); } else { vscode.window.showInformationMessage(msg); } }, ); }