98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
|
|
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<void> {
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
);
|
||
|
|
}
|