posimai-root/posimai-guard-ext/src/commands/scanWorkspace.ts

98 lines
3.4 KiB
TypeScript
Raw Normal View History

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);
}
},
);
}