diff --git a/.github/workflows/checkmarx-one-scan.yml b/.github/workflows/checkmarx-one-scan.yml index 80e5e3021..cd64f0079 100644 --- a/.github/workflows/checkmarx-one-scan.yml +++ b/.github/workflows/checkmarx-one-scan.yml @@ -9,7 +9,7 @@ jobs: - name: Checkout uses: actions/checkout@v4.1.0 - name: Checkmarx One CLI Action - uses: checkmarx/ast-github-action@6c56658230f79c227a55120e9b24845d574d5225 #2.0.31 + uses: checkmarx/ast-github-action@main with: base_uri: ${{ secrets.AST_RND_SCANS_BASE_URI }} cx_tenant: ${{ secrets.AST_RND_SCANS_TENANT }} diff --git a/package.json b/package.json index 9eaca81de..ad097d9b1 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,19 @@ "*" ], "main": "./out/extension.js", - "contributes": { - "commands": [ + "contributes": { "commands": [ + { + "command": "ast-results.testCopilotChatIntegration", + "category": "ast-results", + "title": "Test Copilot Chat Integration" + }, + { + "command": "ast-results.openCopilotChat", + "category": "ast-results", + "title": "Ask Copilot About Vulnerability", + "icon": "$(comment-discussion)", + "enablement": "ast-results.isValidCredentials" + }, { "command": "ast-results.newDetails", "title": "Details" @@ -504,6 +515,10 @@ "title": "Run SCA Realtime Scan", "icon": "$(notebook-execute)", "enablement": "ast-results.isSCAScanEnabled" + }, + { + "command": "ast-results.openCopilotChatWithQuery", + "title": "Open Copilot Chat with Query" } ], "submenus": [ @@ -586,8 +601,17 @@ "group": "navigation@2", "when": "!ast-results-Ignored" } - ], - "view/item/context": [ + ], "view/item/context": [ + { + "command": "ast-results.openCopilotChat", + "group": "inline@1", + "when": "viewItem == vulnerability-item" + }, + { + "command": "ast-results.debugTreeItem", + "group": "inline@2", + "when": "viewItem == vulnerability-item" + }, { "command": "ast-results.projectPick", "group": "inline@3", @@ -882,11 +906,11 @@ } }, { - "title": "Activate Vorpal Real-time Scanning", - "id": "vorpal", + "title": "Activate ASCA", + "id": "asca", "order": 2, "properties": { - "CheckmarxVorpal.Activate Vorpal Real-time Scanning": { + "Checkmarx AI Secure Coding Assistant (ASCA).Activate ASCA": { "type": "boolean", "order": 3, "default": false, @@ -916,8 +940,8 @@ "@typescript-eslint/eslint-plugin": "^7.0.1", "@typescript-eslint/parser": "^7.2.0", "chai": "4.3.1", - "eslint-config-prettier": "^9.1.0", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", "mocha": "10.3.0", "typescript": "^5.5.3", "vsce": "^2.15.0", @@ -927,7 +951,7 @@ "webpack-cli": "^5.1.4" }, "dependencies": { - "@checkmarxdev/ast-cli-javascript-wrapper": "0.0.113", + "@checkmarxdev/ast-cli-javascript-wrapper": "0.0.114", "copyfiles": "2.4.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-node": "^11.1.0", diff --git a/src/vorpal/vorpalService.ts b/src/asca/ascaService.ts similarity index 66% rename from src/vorpal/vorpalService.ts rename to src/asca/ascaService.ts index 8404508a6..324da5253 100644 --- a/src/vorpal/vorpalService.ts +++ b/src/asca/ascaService.ts @@ -5,16 +5,16 @@ import path from "path"; import * as os from "os"; import { error } from "console"; import { Logs } from "../models/logs"; -import CxVorpal from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/vorpal/CxVorpal"; +import CxAsca from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/asca/CxAsca"; import { constants } from "../utils/common/constants"; -const vorpalDir = "CxVorpal"; +const ascaDir = "CxVorpal"; export const diagnosticCollection = vscode.languages.createDiagnosticCollection( constants.extensionFullName ); -export async function scanVorpal(document: vscode.TextDocument, logs: Logs) { +export async function scanAsca(document: vscode.TextDocument, logs: Logs) { if (ignoreFiles(document)) {return;} @@ -24,30 +24,30 @@ export async function scanVorpal(document: vscode.TextDocument, logs: Logs) { path.basename(document.uri.fsPath), document.getText() ); - // RUN VORPAL SCAN - logs.info("Start Vorpal Scan On File: " + document.uri.fsPath); - const scanVorpalResult = await cx.scanVorpal(filePath); + // RUN ASCA SCAN + logs.info("Start ASCA scan On File: " + document.uri.fsPath); + const scanAscaResult = await cx.scanAsca(filePath); // DELETE TEMP FILE deleteFile(filePath); console.info("file %s deleted", filePath); // HANDLE ERROR - if (scanVorpalResult.error) { + if (scanAscaResult.error) { logs.warn( - "Vorpal Warning: " + - (scanVorpalResult.error.description ?? scanVorpalResult.error) + "ASCA Warning: " + + (scanAscaResult.error.description ?? scanAscaResult.error) ); return; } // VIEW PROBLEMS logs.info( - scanVorpalResult.scanDetails.length + - " security best coding practices issues were found in " + + scanAscaResult.scanDetails.length + + " security best practice violations were found in " + document.uri.fsPath ); - updateProblems(scanVorpalResult, document.uri); + updateProblems(scanAscaResult, document.uri); } catch (error) { console.error(error); - logs.error(constants.errorScanVorpal); + logs.error(constants.errorScanAsca); } } @@ -56,16 +56,16 @@ function ignoreFiles(document: vscode.TextDocument): boolean { return document.uri.scheme !== 'file'; } -export async function clearVorpalProblems() { +export async function clearAscaProblems() { diagnosticCollection.clear(); } -function updateProblems(scanVorpalResult: CxVorpal, uri: vscode.Uri) { +function updateProblems(scanAscaResult: CxAsca, uri: vscode.Uri) { diagnosticCollection.delete(uri); const diagnostics: vscode.Diagnostic[] = []; - for (let i = 0; i < scanVorpalResult.scanDetails.length; i++) { - const res = scanVorpalResult.scanDetails[i]; + for (let i = 0; i < scanAscaResult.scanDetails.length; i++) { + const res = scanAscaResult.scanDetails[i]; const range = new vscode.Range( new vscode.Position(res.line - 1, 0), new vscode.Position(res.line - 1, 100) @@ -75,13 +75,13 @@ function updateProblems(scanVorpalResult: CxVorpal, uri: vscode.Uri) { `${res.ruleName} - ${res.remediationAdvise}`, parseSeverity(res.severity) ); - diagnostic.source = constants.vorpalEngineName; + diagnostic.source = constants.ascaEngineName; diagnostics.push(diagnostic); } diagnosticCollection.set(uri, diagnostics); } -function parseSeverity(vorpalSeverity: string): vscode.DiagnosticSeverity { +function parseSeverity(ascaSeverity: string): vscode.DiagnosticSeverity { const severityMap: Record = { CRITICAL: vscode.DiagnosticSeverity.Error, HIGH: vscode.DiagnosticSeverity.Error, @@ -89,10 +89,10 @@ function parseSeverity(vorpalSeverity: string): vscode.DiagnosticSeverity { LOW: vscode.DiagnosticSeverity.Information }; - const severity = severityMap[vorpalSeverity.toUpperCase()]; + const severity = severityMap[ascaSeverity.toUpperCase()]; if (severity === undefined) { - console.log(`Invalid vorpalSeverity value: ${vorpalSeverity}`); + console.log(`Invalid ASCASeverity value: ${ascaSeverity}`); return vscode.DiagnosticSeverity.Information; } @@ -102,7 +102,7 @@ function parseSeverity(vorpalSeverity: string): vscode.DiagnosticSeverity { function saveTempFile(fileName: string, content: string): string | null { try { const tempDir = os.tmpdir(); - const tempFilePath = path.join(tempDir, vorpalDir, fileName); + const tempFilePath = path.join(tempDir, ascaDir, fileName); fs.writeFileSync(tempFilePath, content); console.info("Temp file was saved in: " + tempFilePath); return tempFilePath; @@ -112,9 +112,9 @@ function saveTempFile(fileName: string, content: string): string | null { } } -export async function installVorpal(logs: Logs) { +export async function installAsca(logs: Logs) { try { - const res = await cx.installVorpal(); + const res = await cx.installAsca(); if (res.error) { const errorMessage = constants.errorInstallation + " : " + res.error; vscode.window.showErrorMessage(errorMessage); diff --git a/src/commands/vorpalCommand.ts b/src/commands/ascaCommand.ts similarity index 61% rename from src/commands/vorpalCommand.ts rename to src/commands/ascaCommand.ts index 22a8a7192..a18c326d8 100644 --- a/src/commands/vorpalCommand.ts +++ b/src/commands/ascaCommand.ts @@ -1,14 +1,14 @@ import * as vscode from "vscode"; import { Logs } from "../models/logs"; import { - clearVorpalProblems, - installVorpal, - scanVorpal, -} from "../vorpal/vorpalService"; + clearAscaProblems, + installAsca, + scanAsca, +} from "../asca/ascaService"; import { constants } from "../utils/common/constants"; let timeout = null; -export class VorpalCommand { +export class AscaCommand { context: vscode.ExtensionContext; logs: Logs; onDidChangeTextDocument: vscode.Disposable; @@ -16,38 +16,38 @@ export class VorpalCommand { this.context = context; this.logs = logs; } - public async registerVorpal() { + public async registerAsca() { try { - const vorpalActive = vscode.workspace - .getConfiguration(constants.CheckmarxVorpal) - .get(constants.ActivateVorpalAutoScanning) as boolean; - if (vorpalActive) { - await this.installVorpal(); - await this.registerVorpalScanOnChangeText(); - this.logs.info(constants.vorpalStart); + const ascaActive = vscode.workspace + .getConfiguration(constants.CheckmarxAsca) + .get(constants.ActivateAscaAutoScanning) as boolean; + if (ascaActive) { + await this.installAsca(); + await this.registerAscaScanOnChangeText(); + this.logs.info(constants.ascaStart); } else { - await this.disposeVorpalScanOnChangeText(); - await clearVorpalProblems(); - this.logs.info(constants.vorpalDisabled); + await this.disposeAscaScanOnChangeText(); + await clearAscaProblems(); + this.logs.info(constants.ascaDisabled); } } catch (error) { console.error(error); } } - public installVorpal() { - installVorpal(this.logs); + public installAsca() { + installAsca(this.logs); this.onDidChangeTextDocument = vscode.workspace.onDidChangeTextDocument( - // Must be no less than 2000ms. Otherwise, the temporary file can be deleted before the vorpal scan is finished. + // Must be no less than 2000ms. Otherwise, the temporary file can be deleted before the ASCA scan is finished. this.debounce(this.onTextChange, 2000) ); } public onTextChange(event) { try { - scanVorpal(event.document, this.logs); + scanAsca(event.document, this.logs); } catch (error) { console.error(error); - this.logs.warn("fail to scan vorpal"); + this.logs.warn("fail to scan ASCA"); } } // Debounce function @@ -68,10 +68,10 @@ export class VorpalCommand { }; } - public registerVorpalScanOnChangeText() { + public registerAscaScanOnChangeText() { this.context.subscriptions.push(this.onDidChangeTextDocument); } - public disposeVorpalScanOnChangeText() { + public disposeAscaScanOnChangeText() { if (this.onDidChangeTextDocument) { this.onDidChangeTextDocument.dispose(); this.context.subscriptions.push(this.onDidChangeTextDocument); diff --git a/src/commands/copilotChatCommand.ts b/src/commands/copilotChatCommand.ts new file mode 100644 index 000000000..8f5c88567 --- /dev/null +++ b/src/commands/copilotChatCommand.ts @@ -0,0 +1,298 @@ +import * as vscode from "vscode"; +import { Logs } from "../models/logs"; +import { commands } from "../utils/common/commands"; +import { constants } from "../utils/common/constants"; +import { AstResult } from "../models/results"; + +export class CopilotChatCommand { + context: vscode.ExtensionContext; + logs: Logs; + + constructor(context: vscode.ExtensionContext, logs: Logs) { + this.context = context; + this.logs = logs; + } + + /** + * Detects if we're running in Cursor IDE + */ + private isCursorIDE(): boolean { + try { + // Check if the application name contains "Cursor" + const appName = vscode.env.appName || ''; + if (appName.toLowerCase().includes('cursor')) { + return true; + } + + // Alternative check: try to see if Cursor-specific extensions are installed + const cursorExtensions = vscode.extensions.all.filter(ext => + ext.id.toLowerCase().includes('cursor') || + (ext.packageJSON?.publisher?.toLowerCase()?.includes('cursor')) + ); + + return cursorExtensions.length > 0; + } catch (err) { + this.logs.error(`Error detecting IDE type: ${err}`); + return false; // Default to VS Code if detection fails + } + } + + /** + * Helper method to ensure Cursor chat is focused + * Makes multiple attempts to focus the chat input + * @returns True if focusing was successful + */ + private async focusCursorChat(): Promise { + try { + // Try the composer.focusInput command first (Cursor-specific) + try { + await vscode.commands.executeCommand("composer.focusInput"); + return true; + } catch (err) { + this.logs.info("composer.focusInput failed, trying alternative methods"); + } + + // Try clicking on the chat input area (more generic approach) + try { + // This simulates clicking in the editor area which might contain the chat + await vscode.commands.executeCommand("workbench.action.focusActiveEditorGroup"); + return true; + } catch (err) { + this.logs.error(`Failed to focus on chat: ${err}`); + } + + return false; + } catch (err) { + this.logs.error(`Error in focusCursorChat: ${err}`); + return false; + } + } + + /** + * Handles opening chat and sending a question in Cursor IDE + * @param question The question to send to Cursor AI + * @returns True if successful, false if all methods failed + */ + private async handleCursorIDE(question: string): Promise { + try { + this.logs.info("Handling Cursor IDE integration"); + + // Copy the question to clipboard as a fallback right away + await vscode.env.clipboard.writeText(question); + const formattedQuestion = { + text: question, + from: 'user' + }; + "editor.action.en" + + // Step 1: Try to open the Cursor chat with composer.startComposerPrompt + this.logs.info("Opening Cursor chat with composer.startComposerPrompt"); + try { // Try to open the chat with the question + await vscode.commands.executeCommand("composer.startComposerPrompt"); + // await vscode.commands.executeCommand("composer.newAgentChat", "massage to chat ai"); + await vscode.commands.executeCommand("composer.fixerrormessage", "Fix this problem"); + try { + // Try to paste the text + await vscode.commands.executeCommand("editor.action.clipboardPasteAction"); + this.logs.info("Programmatically pasted content from clipboard"); + + // Wait a moment for paste to complete + await new Promise(resolve => setTimeout(resolve, 1000)); + + + // Send Enter key to submit the message - try cell execution first + try { + await vscode.commands.executeCommand("notebook.cell.execute"); + this.logs.info("Executed cell to send message"); + } catch (cellErr) { + // If cell execution fails, try simulating Enter key directly + this.logs.info("Cell execution failed, trying direct Enter key press"); + await vscode.commands.executeCommand('type', { text: '\n' }); + } + + vscode.window.showInformationMessage("Question pasted and sent to Cursor chat"); + } catch (pasteErr) { + this.logs.error(`Failed to programmatically paste: ${pasteErr}`); + vscode.window.showInformationMessage("Question copied to clipboard. Please paste it into the Cursor chat."); + } + + // await vscode.commands.executeCommand(constants.cursorComposerMessage, question) + + this.logs.info("Successfully opened Cursor Composer with question"); + // Step 2: Try to send the question using composer.sendToAgent + try { + // Wait a moment to ensure the chat is ready + await new Promise(resolve => setTimeout(resolve, 10000)); + this.logs.info("Sending question to Cursor chat with composer.sendToAgent"); + + // Format the question as an object with the proper structure + // const formattedQuestion = { + // text: question, + // from: 'user' + // }; + + await vscode.commands.executeCommand(constants.cursorComposerSender, formattedQuestion); + this.logs.info("Successfully sent question to Cursor chat"); + vscode.window.showInformationMessage("Query sent to Cursor AI"); + return true; + } catch (sendErr) { + this.logs.error(`Failed to send question to Cursor chat: ${sendErr}`); + // Chat is open but sending failed - inform user to paste from clipboard + vscode.window.showInformationMessage("Question copied to clipboard. Please paste it into the Cursor chat."); + return true; // Still return true as we successfully opened the chat + } + } catch (openErr) { + this.logs.error(`Failed to open Cursor chat: ${openErr}`); + vscode.window.showInformationMessage("Could not open Cursor chat. Question copied to clipboard."); + return false; + } + } catch (err) { + this.logs.error(`Error in Cursor IDE integration: ${err}`); + vscode.window.showInformationMessage("Question copied to clipboard. Please open Cursor Chat manually."); + return false; + } + } + + public registerCopilotChatCommand() { + this.context.subscriptions.push( + vscode.commands.registerCommand(commands.openCopilotChat, async (item: any) => { + try { + // Log the action + this.logs.info("Opening Copilot Chat to ask about vulnerability"); + + // Debug: Log information about the item + this.logs.info(`Item type: ${typeof item}`); + this.logs.info(`Item properties: ${Object.keys(item).join(', ')}`); + this.logs.info(`Item contextValue: ${item.contextValue}`); + this.logs.info(`Has result property: ${item.result !== undefined}`); + + // Get vulnerability details from the selected item + const result = item?.result as AstResult; + if (!result) { + vscode.window.showErrorMessage("No vulnerability details found. Please select a valid vulnerability."); + return; + } + + // Debug: Log information about the result + this.logs.info(`Result type: ${typeof result}`); + this.logs.info(`Result properties: ${Object.keys(result).join(', ')}`); + + // Extract relevant information from the vulnerability + const vulnName = result.queryName || result.label || "security vulnerability"; + const severity = result.severity || "High"; + const language = result.language || ""; + const category = result.type || ""; + const fileName = result.fileName || ""; + const description = result.description || ""; + + // Construct a more detailed message to send to Copilot + const question = `Can you explain this ${severity} severity security vulnerability: "${vulnName}" detected in ${language} code? +It was found in file ${fileName} and categorized as ${category}. +${description ? `Additional description: ${description}` : ''} +How can I fix it in my code?`; + + // Show info message to user + vscode.window.showInformationMessage("Opening Copilot Chat to ask about this vulnerability"); + + // Check if Copilot Chat is available + try { + // Try to find the Copilot Chat extension + const copilotChatExtension = vscode.extensions.getExtension(constants.copilotChatExtensionId); + if (!copilotChatExtension) { + // Copilot Chat not installed - show installation message + const installOption = "Install Copilot Chat"; + const choice = await vscode.window.showErrorMessage( + "GitHub Copilot Chat extension is not installed. Install it to use this feature.", + installOption + ); + + if (choice === installOption) { + // Open the extension in marketplace + await vscode.commands.executeCommand('workbench.extensions.search', `@id:${constants.copilotChatExtensionId}`); + } + return; + } + + // Detect if we're running in Cursor IDE + const isCursorIDE = this.isCursorIDE(); + this.logs.info(`Detected environment: ${isCursorIDE ? 'Cursor IDE' : 'VS Code'}`); + if (isCursorIDE) { + // Try Cursor IDE specific commands + const cursorSuccess = await this.handleCursorIDE(question); + if (cursorSuccess) { + return; + } else { + vscode.window.showInformationMessage("Question copied to clipboard. Please open Cursor Chat and paste it."); + return; + } + } + + // If not in Cursor, try to open Copilot Chat with the modern approach (direct query) + try { + // Try the modern command that opens chat with a query directly + this.logs.info("Trying modern Copilot Chat opening approach"); + await vscode.commands.executeCommand( + constants.copilotNewChatOpenWithQueryCommand); + // Wait for the chat to open + await vscode.commands.executeCommand( + constants.copilotChatOpenWithQueryCommand, + { query: `@copilot ${question}` } + ); + return; // If successful, exit the function + } catch (err) { + // If the modern approach fails, fall back to the older methods + this.logs.info(`Modern Copilot Chat command failed: ${err}. Trying older methods.`); + + // Try older methods to open Copilot Chat + try { + // First try the standard command + await vscode.commands.executeCommand(constants.copilotShowCommand); + + // Use a slight delay to ensure the chat window is ready + setTimeout(async () => { + try { + // Send the question to Copilot + await vscode.commands.executeCommand(constants.copilotSendRequestCommand, question); + } catch (err) { + // If sending fails, automatically copy to clipboard + await vscode.env.clipboard.writeText(question); + vscode.window.showInformationMessage("Question copied to clipboard. Please paste it into Copilot Chat."); + } + }, 1000); + } catch (err) { + // If the standard command fails, try alternative commands + this.logs.info(`First Copilot command failed: ${err}. Trying alternative.`); + try { + // Try alternative command (Copilot might use different command in some versions) + await vscode.commands.executeCommand(constants.copilotFocusCommand); + + // Use a slight delay to ensure the chat window is ready + setTimeout(async () => { + try { + await vscode.commands.executeCommand(constants.copilotSendRequestCommand, question); + } catch (e) { + // If sending the question fails, automatically copy and notify the user + await vscode.env.clipboard.writeText(question); + vscode.window.showInformationMessage("Question copied to clipboard. Please paste it into Copilot Chat."); + } + }, 1000); + } catch (e) { + // If all commands fail, automatically copy the question to clipboard + this.logs.error(`Could not open Copilot Chat with any command: ${e}`); + await vscode.env.clipboard.writeText(question); + vscode.window.showInformationMessage("Question copied to clipboard. Please open Copilot Chat and paste it."); + } + } + } + } catch (err) { + this.logs.error(`Error interacting with Copilot Chat: ${err}`); + vscode.window.showErrorMessage(`Copilot Chat interaction error: ${err.message}`); + } + } catch (error) { + this.logs.error(`Error opening Copilot Chat: ${error}`); + vscode.window.showErrorMessage(`Failed to open Copilot Chat: ${error}`); + } + }) + ); + } +} diff --git a/src/commands/debugCommand.ts b/src/commands/debugCommand.ts new file mode 100644 index 000000000..9ea5cf817 --- /dev/null +++ b/src/commands/debugCommand.ts @@ -0,0 +1,45 @@ +import * as vscode from "vscode"; +import { Logs } from "../models/logs"; +import { constants } from "../utils/common/constants"; + +export class DebugCommand { + context: vscode.ExtensionContext; + logs: Logs; + + constructor(context: vscode.ExtensionContext, logs: Logs) { + this.context = context; + this.logs = logs; + } + + public registerDebugCommand() { + // Register a command to log information about tree items + this.context.subscriptions.push( + vscode.commands.registerCommand(`${constants.extensionName}.debugTreeItem`, async (item: any) => { + try { + this.logs.info("Debug tree item called"); + this.logs.info(`Item type: ${typeof item}`); + + if (item) { + this.logs.info(`Item properties: ${Object.keys(item).join(', ')}`); + this.logs.info(`Item contextValue: ${item.contextValue}`); + this.logs.info(`Item label: ${item.label}`); + this.logs.info(`Has result property: ${item.result !== undefined}`); + + if (item.result) { + this.logs.info(`Result type: ${typeof item.result}`); + this.logs.info(`Result properties: ${Object.keys(item.result).join(', ')}`); + this.logs.info(`Result severity: ${item.result.severity}`); + this.logs.info(`Result label: ${item.result.label}`); + } + } else { + this.logs.info("Item is undefined"); + } + + vscode.window.showInformationMessage("Debug info logged to output channel"); + } catch (error) { + this.logs.error(`Error in debug command: ${error}`); + } + }) + ); + } +} diff --git a/src/cx/cx.ts b/src/cx/cx.ts index 0622d4986..5fe6cb9ac 100644 --- a/src/cx/cx.ts +++ b/src/cx/cx.ts @@ -15,7 +15,7 @@ import { CxPlatform } from "./cxPlatform"; import { CxCommandOutput } from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/wrapper/CxCommandOutput"; import { ChildProcessWithoutNullStreams } from "child_process"; import CxLearnMoreDescriptions from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/learnmore/CxLearnMoreDescriptions"; -import CxVorpal from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/vorpal/CxVorpal"; +import CxAsca from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/asca/CxAsca"; import { messages } from "../utils/common/messages"; export class Cx implements CxPlatform { async scaScanCreate(sourcePath: string): Promise { @@ -418,38 +418,38 @@ export class Cx implements CxPlatform { statusBarItem.text = text; show ? statusBarItem.show() : statusBarItem.hide(); } - async installVorpal(): Promise { + async installAsca(): Promise { let config = this.getAstConfiguration(); if (!config) { config = new CxConfig(); } const cx = new CxWrapper(config); - const scans = await cx.scanVorpal(null, true, constants.vsCodeAgent); + const scans = await cx.scanAsca(null, true, constants.vsCodeAgent); if (scans.payload && scans.exitCode === 0) { return scans.payload[0]; } else { - return this.getVorpalError(scans.status, "Failed to run vorpal engine"); + return this.getAscaError(scans.status, "Failed to run ASCA engine"); } } - private getVorpalError(scanStatus: string, errorMessage: string) { + private getAscaError(scanStatus: string, errorMessage: string) { console.error(errorMessage); - const errorRes = new CxVorpal(); + const errorRes = new CxAsca(); errorRes.error = scanStatus; return errorRes; } - async scanVorpal(sourcePath: string): Promise { + async scanAsca(sourcePath: string): Promise { let config = this.getAstConfiguration(); if (!config) { config = new CxConfig(); } const cx = new CxWrapper(config); - const scans = await cx.scanVorpal(sourcePath, false, constants.vsCodeAgent); + const scans = await cx.scanAsca(sourcePath, false, constants.vsCodeAgent); if (scans.payload && scans.exitCode === 0) { return scans.payload[0]; } else { - return this.getVorpalError(scans.status, "Fail to call vorpal scan"); + return this.getAscaError(scans.status, "Fail to call ASCA scan"); } } } diff --git a/src/cx/cxMock.ts b/src/cx/cxMock.ts index ffedea467..370e7b653 100644 --- a/src/cx/cxMock.ts +++ b/src/cx/cxMock.ts @@ -7,7 +7,7 @@ import { CxConfig } from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/wra import { getFilePath } from "../utils/utils"; import { writeFileSync } from "fs"; import { CxPlatform } from "./cxPlatform"; -import CxVorpal from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/vorpal/CxVorpal"; +import CxAsca from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/asca/CxAsca"; import { EMPTY_RESULTS_SCAN_ID } from "../test/utils/envs"; export class CxMock implements CxPlatform { @@ -1169,11 +1169,11 @@ export class CxMock implements CxPlatform { show ? statusBarItem.show() : statusBarItem.hide(); } - installVorpal(): Promise { + installAsca(): Promise { return null; } - async scanVorpal(sourcePath: string): Promise { - return new CxVorpal(); + async scanAsca(sourcePath: string): Promise { + return new CxAsca(); } } diff --git a/src/cx/cxPlatform.ts b/src/cx/cxPlatform.ts index 8d5315807..e8b3e3694 100644 --- a/src/cx/cxPlatform.ts +++ b/src/cx/cxPlatform.ts @@ -8,7 +8,7 @@ import { Logs } from "../models/logs"; import { ChildProcessWithoutNullStreams } from "child_process"; import { CxCommandOutput } from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/wrapper/CxCommandOutput"; import CxLearnMoreDescriptions from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/learnmore/CxLearnMoreDescriptions"; -import CxVorpal from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/vorpal/CxVorpal"; +import CxAsca from "@checkmarxdev/ast-cli-javascript-wrapper/dist/main/asca/CxAsca"; export interface CxPlatform { /** @@ -179,14 +179,14 @@ export interface CxPlatform { updateStatusBarItem(text: string, show: boolean, statusBarItem: vscode.StatusBarItem); /** - * install the Vorpal engine + * install the ASCA engine */ - installVorpal(): Promise; + installAsca(): Promise; /** - * Scan the edited file in the vorpal engine and show the results in the problem section - * @param sourcePath the edited file sent to the vorpal engine + * Scan the edited file in the ASCA engine and show the results in the problem section + * @param sourcePath the edited file sent to the ASCA engine */ - scanVorpal(sourcePath: string): Promise; + scanAsca(sourcePath: string): Promise; } diff --git a/src/extension.ts b/src/extension.ts index 85ae1f6f1..f24d9553d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -23,7 +23,9 @@ import { WorkspaceListener } from "./utils/listener/workspaceListener"; import { DocAndFeedbackView } from "./views/docsAndFeedbackView/docAndFeedbackView"; import { messages } from "./utils/common/messages"; import { commands } from "./utils/common/commands"; -import { VorpalCommand } from "./commands/vorpalCommand"; +import { AscaCommand } from "./commands/ascaCommand"; +import { CopilotChatCommand } from "./commands/copilotChatCommand"; +import { DebugCommand } from "./commands/debugCommand"; export async function activate(context: vscode.ExtensionContext) { // Create logs channel and make it visible @@ -176,8 +178,8 @@ export async function activate(context: vscode.ExtensionContext) { } } }); - const vorpalCommand = new VorpalCommand(context, logs); - vorpalCommand.registerVorpal(); + const ascaCommand = new AscaCommand(context, logs); + ascaCommand.registerAsca(); // Register Settings const commonCommand = new CommonCommand(context, logs); commonCommand.registerSettings(); @@ -189,7 +191,7 @@ export async function activate(context: vscode.ExtensionContext) { // SCA auto scanning enablement await commonCommand.executeCheckScaScanEnabled(); // execute command to listen to settings change - await executeCheckSettingsChange(kicsStatusBarItem, logs, vorpalCommand); + await executeCheckSettingsChange(kicsStatusBarItem, logs, ascaCommand); const treeCommand = new TreeCommand( context, @@ -208,6 +210,48 @@ export async function activate(context: vscode.ExtensionContext) { // Register pickers command const pickerCommand = new PickerCommand(context, logs, astResultsProvider); pickerCommand.registerPickerCommands(); + // Register Copilot Chat integration command + const copilotChatCommand = new CopilotChatCommand(context, logs); + copilotChatCommand.registerCopilotChatCommand(); + + // Register a test command to debug the Copilot Chat integration + context.subscriptions.push( + vscode.commands.registerCommand('ast-results.testCopilotChatIntegration', async () => { + try { + logs.info("Testing Copilot Chat integration"); + + // Create a dummy tree item with vulnerability data + const dummyVulnerability = { + queryName: "Test Vulnerability", + label: "Test Vulnerability", + severity: "HIGH", + language: "JavaScript", + type: "SAST", + fileName: "test.js", + description: "This is a test vulnerability to check if the Copilot Chat integration is working." + }; + + // Try to execute the openCopilotChat command directly + logs.info("Executing openCopilotChat command with dummy data"); + await vscode.commands.executeCommand('ast-results.openCopilotChat', { + contextValue: 'vulnerability-item', + label: 'Test Vulnerability', + result: dummyVulnerability + }); + + logs.info("Test completed successfully"); + vscode.window.showInformationMessage("Copilot Chat integration test completed. Check the output panel for details."); + } catch (error) { + logs.error(`Error testing Copilot Chat integration: ${error}`); + vscode.window.showErrorMessage(`Copilot Chat integration test failed: ${error}`); + } + }) + ); + + // Register debug command for troubleshooting + const debugCommand = new DebugCommand(context, logs); + debugCommand.registerDebugCommand(); + // Visual feedback on wrapper errors commonCommand.registerErrors(); // Registe Kics remediation command diff --git a/src/test/1.settings.test.ts b/src/test/1.settings.test.ts index be5e5362f..33a4767b5 100644 --- a/src/test/1.settings.test.ts +++ b/src/test/1.settings.test.ts @@ -67,24 +67,24 @@ describe("Extension settings tests", () => { expect(enablement).to.equal(true); }); - it("verify vorpal checkbox exists in the settings", async function () { + it("verify ASCA checkbox exists in the settings", async function () { settingsEditor = await bench.openSettings(); - const vorpalCheckbox = await settingsEditor.findSetting( - constants.ActivateVorpalAutoScanning, - constants.CheckmarxVorpal + const ascaCheckbox = await settingsEditor.findSetting( + constants.ActivateAscaAutoScanning, + constants.CheckmarxAsca ); - let vorpalCheckboxValue = await vorpalCheckbox.getValue(); - expect(vorpalCheckboxValue).to.not.be.undefined; + let ascaCheckboxValue = await ascaCheckbox.getValue(); + expect(ascaCheckboxValue).to.not.be.undefined; }); - it("vorpal starts when the Vorpal checkbox is True in settings", async function () { + it("ASCA starts when the ASCA checkbox is True in settings", async function () { settingsEditor = await bench.openSettings(); - const vorpalCheckbox = await settingsEditor.findSetting( - constants.ActivateVorpalAutoScanning, - constants.CheckmarxVorpal + const ascaCheckbox = await settingsEditor.findSetting( + constants.ActivateAscaAutoScanning, + constants.CheckmarxAsca ); - await vorpalCheckbox.setValue(true); - let vorpalCheckboxValue = await vorpalCheckbox.getValue(); - expect(vorpalCheckboxValue).to.be.true; + await ascaCheckbox.setValue(true); + let ascaCheckboxValue = await ascaCheckbox.getValue(); + expect(ascaCheckboxValue).to.be.true; }); }); diff --git a/src/utils/common/commands.ts b/src/utils/common/commands.ts index 1225cd77c..148bd6da7 100644 --- a/src/utils/common/commands.ts +++ b/src/utils/common/commands.ts @@ -12,6 +12,8 @@ export const commands = { isScanEnabled: `${constants.extensionName}.isScanEnabled`, isScaScanEnabled: `${constants.extensionName}.isSCAScanEnabled`, + openCopilotChat: `${constants.extensionName}.openCopilotChat`, + filterCriticalToggle: `${constants.extensionName}.filterCritical_toggle`, filterCriticalUntoggle: `${constants.extensionName}.filterCritical_untoggle`, filterCritical: `${constants.extensionName}.filterCritical`, diff --git a/src/utils/common/constants.ts b/src/utils/common/constants.ts index 127476480..0a3a9fc15 100644 --- a/src/utils/common/constants.ts +++ b/src/utils/common/constants.ts @@ -47,6 +47,7 @@ export const constants = { requestChangesItem: "requestChanges-item", mailItem: "mail-item", calendarItem: "calendar-item", + vulnerabilityItem: "vulnerability-item", resultsFileName: "ast-results", resultsFileExtension: "json", status: [ @@ -145,20 +146,36 @@ export const constants = { // TRIAGE triageUpdate: "ast-result-triage", - // Vorpal engine - errorInstallation: "Failed to run vorpal engine", - errorScanVorpal: "failed to handle vorpal scan", - vorpalStart: "Vorpal engine started", - vorpalDisabled: "Vorpal Real-time Scanning is disabled now.", - vorpalEngineName: "Vorpal", - ActivateVorpalAutoScanning: "Activate Vorpal Real-time Scanning", - CheckmarxVorpal: "CheckmarxVorpal", - + // ASCA engine + errorInstallation: "Failed to run ASCA engine", + errorScanAsca: "failed to handle ASCA scan", + ascaStart: "AI Secure Coding Assistant Engine started", + ascaDisabled: "AI Secure Coding Assistant Engine disabled", + ascaEngineName: "ASCA", + ActivateAscaAutoScanning: "Activate ASCA", + CheckmarxAsca: "Checkmarx AI Secure Coding Assistant (ASCA)", criticalSeverity: "CRITICAL", highSeverity: "HIGH", mediumSeverity: "MEDIUM", lowSeverity: "LOW", - infoSeverity: "INFO", + infoSeverity: "INFO", // Copilot integration + copilotChatExtensionId: "GitHub.copilot-chat", + copilotShowCommand: "github.copilot.chat.show", + copilotFocusCommand: "github.copilot.chat.focus", + copilotSendRequestCommand: "github.copilot.chat.sendRequest", + // Modern way to open Copilot Chat with a query + copilotNewChatOpenWithQueryCommand: "workbench.action.chat.newChat", + copilotChatOpenWithQueryCommand: "workbench.action.chat.open", + // Cursor IDE specific commands + cursorChatOpenCommand: "cursor.openChat", + cursorChatSendCommand: "cursor.chat.submit", + cursorComposerCommand: "composer.startComposerPrompt", + cursorComposerMessage: "composer.fixerrormessage", + // Alternative ways to open Cursor chat + cursorComposerSender: "composer.sendToAgent", + // cursorComposerSender: "notebook.cell.chat.accept", + + cursorChatAlternativeCommand: "workbench.action.terminal.sendSequence", }; export enum GroupBy { diff --git a/src/utils/listener/listeners.ts b/src/utils/listener/listeners.ts index 36129e051..eefc8ddb7 100644 --- a/src/utils/listener/listeners.ts +++ b/src/utils/listener/listeners.ts @@ -9,7 +9,7 @@ import { getFromState, updateState } from "../common/globalState"; import { cx } from "../../cx"; import { getGitAPIRepository, isKicsFile, isSystemFile } from "../utils"; import { messages } from "../common/messages"; -import { VorpalCommand } from "../../commands/vorpalCommand"; +import { AscaCommand } from "../../commands/ascaCommand"; export async function getBranchListener( context: vscode.ExtensionContext, @@ -169,7 +169,7 @@ export async function gitExtensionListener( export async function executeCheckSettingsChange( kicsStatusBarItem: vscode.StatusBarItem, logs: Logs, - vorpalCommand: VorpalCommand + ascaCommand: AscaCommand ) { vscode.workspace.onDidChangeConfiguration(async (event) => { vscode.commands.executeCommand( @@ -190,14 +190,14 @@ export async function executeCheckSettingsChange( ? messages.kicsStatusBarConnect : messages.kicsStatusBarDisconnect; await vscode.commands.executeCommand(commands.refreshTree); - const vorpalEffected = event.affectsConfiguration( - `${constants.CheckmarxVorpal}.${constants.ActivateVorpalAutoScanning}` + const ascaEffected = event.affectsConfiguration( + `${constants.CheckmarxAsca}.${constants.ActivateAscaAutoScanning}` ); const apikeyEffected = event.affectsConfiguration( "checkmarxOne.apiKey" ); - if (vorpalEffected || apikeyEffected) { - await vorpalCommand.registerVorpal(); + if (ascaEffected || apikeyEffected) { + await ascaCommand.registerAsca(); } }); } diff --git a/src/views/resultsProviders.ts b/src/views/resultsProviders.ts index 570624dd8..653f13900 100644 --- a/src/views/resultsProviders.ts +++ b/src/views/resultsProviders.ts @@ -125,11 +125,20 @@ export class ResultsProvider implements vscode.TreeDataProvider { issueLevel: string[], stateLevel: string[] ) { - const obj = new AstResult(rawObj); - if (!obj || !obj.typeLabel) { + const obj = new AstResult(rawObj); if (!obj || !obj.typeLabel) { return; - } - const item = new TreeItem(obj.label.replaceAll("_", " "), undefined, obj); + } // Always set the context value to vulnerability-item for items with a result property + // This ensures they can be interacted with via the context menu + const item = new TreeItem( + obj.label.replaceAll("_", " "), + constants.vulnerabilityItem, + obj + ); + + // Debug: Log the TreeItem's contextValue + console.log(`TreeItem created with contextValue: ${item.contextValue}, label: ${item.label}`); + console.log(`TreeItem has result: ${item.result !== undefined}`); + let node; // Verify the current severity filters applied if (issueLevel.length > 0) { @@ -146,13 +155,14 @@ export class ResultsProvider implements vscode.TreeDataProvider { folder, map ); - } - node = groups.reduce( + } node = groups.reduce( (previousValue: TreeItem, currentValue: string) => this.reduceGroups(obj, previousValue, currentValue), tree ); node.children?.push(item); + // Debug: Log the node structure + console.log(`Added item to node. Node children count: ${node.children?.length}`); } } }