From 0d4c188eb8438cc902ed225a8f7591653407afbf Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Thu, 19 Jun 2025 15:08:20 -0700 Subject: [PATCH 01/29] feat(amazonq): parse message from agentic reviewer --- packages/amazonq/src/lsp/chat/messages.ts | 61 +++++++++++++++++-- .../commands/startSecurityScan.ts | 4 +- .../src/codewhisperer/models/constants.ts | 2 + .../service/diagnosticsProvider.ts | 1 - 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index d78d33f55e7..1562e544bb3 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -65,7 +65,16 @@ import { v4 as uuidv4 } from 'uuid' import * as vscode from 'vscode' import { Disposable, LanguageClient, Position, TextDocumentIdentifier } from 'vscode-languageclient' import { AmazonQChatViewProvider } from './webviewProvider' -import { AuthUtil, ReferenceLogViewProvider } from 'aws-core-vscode/codewhisperer' +import { + AggregatedCodeScanIssue, + AuthUtil, + CodeAnalysisScope, + CodeWhispererSettings, + initSecurityScanRender, + ReferenceLogViewProvider, + SecurityIssueTreeViewProvider, + CodeWhispererConstants, +} from 'aws-core-vscode/codewhisperer' import { amazonQDiffScheme, AmazonQPromptSettings, messages, openUrl, isTextEditor } from 'aws-core-vscode/shared' import { DefaultAmazonQAppInitContext, @@ -79,6 +88,7 @@ import { isValidResponseError } from './error' import { decryptResponse, encryptRequest } from '../encryption' import { getCursorState } from '../utils' import { focusAmazonQPanel } from './commands' +import { ChatMessage } from '@aws/language-server-runtimes/server-interface' export function registerActiveEditorChangeListener(languageClient: LanguageClient) { let debounceTimer: NodeJS.Timeout | undefined @@ -292,7 +302,8 @@ export function registerMessageListeners( encryptionKey, provider, chatParams.tabId, - chatDisposable + chatDisposable, + languageClient ) } catch (e) { const errorMsg = `Error occurred during chat request: ${e}` @@ -308,7 +319,8 @@ export function registerMessageListeners( encryptionKey, provider, chatParams.tabId, - chatDisposable + chatDisposable, + languageClient ) } finally { chatStreamTokens.delete(chatParams.tabId) @@ -339,7 +351,8 @@ export function registerMessageListeners( encryptionKey, provider, message.params.tabId, - quickActionDisposable + quickActionDisposable, + languageClient ) break } @@ -573,6 +586,11 @@ async function handlePartialResult( ) { const decryptedMessage = await decryptResponse(partialResult, encryptionKey) + decryptedMessage.additionalMessages = decryptedMessage.additionalMessages?.filter( + (message) => + !(message.messageId !== undefined && message.messageId.endsWith(CodeWhispererConstants.findingsSuffix)) + ) + if (decryptedMessage.body !== undefined) { void provider.webview?.postMessage({ command: chatRequestType.method, @@ -593,10 +611,13 @@ async function handleCompleteResult( encryptionKey: Buffer | undefined, provider: AmazonQChatViewProvider, tabId: string, - disposable: Disposable + disposable: Disposable, + languageClient: LanguageClient ) { const decryptedMessage = await decryptResponse(result, encryptionKey) + handleSecurityFindings(decryptedMessage, languageClient) + void provider.webview?.postMessage({ command: chatRequestType.method, params: decryptedMessage, @@ -610,6 +631,36 @@ async function handleCompleteResult( disposable.dispose() } +function handleSecurityFindings( + decryptedMessage: { additionalMessages?: ChatMessage[] }, + languageClient: LanguageClient +): void { + if (decryptedMessage.additionalMessages == undefined || decryptedMessage.additionalMessages.length == 0) { + return + } + for (let i = decryptedMessage.additionalMessages.length; i >= 0; i--) { + const message = decryptedMessage.additionalMessages[i] + if (message.messageId !== undefined && message.messageId.endsWith(CodeWhispererConstants.findingsSuffix)) { + if (message.body != null) { + try { + const aggregatedCodeScanIssue: AggregatedCodeScanIssue = JSON.parse(message.body) + for (const issue of aggregatedCodeScanIssue.issues) { + issue.visible = !CodeWhispererSettings.instance.getIgnoredSecurityIssues().includes(issue.title) + if (issue.suggestedFixes == undefined) { + issue.suggestedFixes = [] + } + } + initSecurityScanRender([aggregatedCodeScanIssue], undefined, CodeAnalysisScope.PROJECT) + SecurityIssueTreeViewProvider.focus() + } catch (e) { + languageClient.info('Failed to parse findings') + } + } + decryptedMessage.additionalMessages.splice(i, 1) + } + } +} + async function resolveChatResponse( requestMethod: string, params: any, diff --git a/packages/core/src/codewhisperer/commands/startSecurityScan.ts b/packages/core/src/codewhisperer/commands/startSecurityScan.ts index d04fe6effc3..5ff6d13bd91 100644 --- a/packages/core/src/codewhisperer/commands/startSecurityScan.ts +++ b/packages/core/src/codewhisperer/commands/startSecurityScan.ts @@ -401,7 +401,7 @@ export function showSecurityScanResults( zipMetadata: ZipMetadata, totalIssues: number ) { - initSecurityScanRender(securityRecommendationCollection, context, editor, scope) + initSecurityScanRender(securityRecommendationCollection, editor, scope) if (scope === CodeWhispererConstants.CodeAnalysisScope.PROJECT) { populateCodeScanLogStream(zipMetadata.scannedFiles) @@ -439,7 +439,7 @@ export function showScanResultsInChat( break } - initSecurityScanRender(securityRecommendationCollection, context, editor, scope) + initSecurityScanRender(securityRecommendationCollection, editor, scope) if (totalIssues > 0) { SecurityIssueTreeViewProvider.focus() } diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 319127cba20..84e99699d79 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -900,3 +900,5 @@ export const predictionTrackerDefaultConfig = { maxAgeMs: 30000, maxSupplementalContext: 15, } + +export const findingsSuffix = '_findings' diff --git a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts index 72407cd80f5..f181bdb146d 100644 --- a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts +++ b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts @@ -25,7 +25,6 @@ export const securityScanRender: SecurityScanRender = { export function initSecurityScanRender( securityRecommendationList: AggregatedCodeScanIssue[], - context: vscode.ExtensionContext, editor: vscode.TextEditor | undefined, scope: CodeAnalysisScope ) { From 983a87d09495d364ddbe864034408b7960f0a526 Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Fri, 20 Jun 2025 14:44:28 -0700 Subject: [PATCH 02/29] feat(amazonq): support array of AggregatedCodeScanIssue objects from flare --- packages/amazonq/src/lsp/chat/messages.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 1562e544bb3..0ff9f1785fc 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -635,22 +635,23 @@ function handleSecurityFindings( decryptedMessage: { additionalMessages?: ChatMessage[] }, languageClient: LanguageClient ): void { - if (decryptedMessage.additionalMessages == undefined || decryptedMessage.additionalMessages.length == 0) { + if (decryptedMessage.additionalMessages === undefined || decryptedMessage.additionalMessages.length === 0) { return } - for (let i = decryptedMessage.additionalMessages.length; i >= 0; i--) { + for (let i = decryptedMessage.additionalMessages.length - 1; i >= 0; i--) { const message = decryptedMessage.additionalMessages[i] if (message.messageId !== undefined && message.messageId.endsWith(CodeWhispererConstants.findingsSuffix)) { - if (message.body != null) { + if (message.body !== undefined) { try { - const aggregatedCodeScanIssue: AggregatedCodeScanIssue = JSON.parse(message.body) - for (const issue of aggregatedCodeScanIssue.issues) { - issue.visible = !CodeWhispererSettings.instance.getIgnoredSecurityIssues().includes(issue.title) - if (issue.suggestedFixes == undefined) { - issue.suggestedFixes = [] + const aggregatedCodeScanIssues: AggregatedCodeScanIssue[] = JSON.parse(message.body) + for (const aggregatedCodeScanIssue of aggregatedCodeScanIssues) { + for (const issue of aggregatedCodeScanIssue.issues) { + issue.visible = !CodeWhispererSettings.instance + .getIgnoredSecurityIssues() + .includes(issue.title) } } - initSecurityScanRender([aggregatedCodeScanIssue], undefined, CodeAnalysisScope.PROJECT) + initSecurityScanRender(aggregatedCodeScanIssues, undefined, CodeAnalysisScope.PROJECT) SecurityIssueTreeViewProvider.focus() } catch (e) { languageClient.info('Failed to parse findings') From 51f72b099def14681f8540345d4f6bb8f8f0c202 Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Fri, 20 Jun 2025 15:48:19 -0700 Subject: [PATCH 03/29] fix(amazonq): change findings suffix to make it more precise --- packages/core/src/codewhisperer/models/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index 84e99699d79..c02ba6ff8bc 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -901,4 +901,4 @@ export const predictionTrackerDefaultConfig = { maxSupplementalContext: 15, } -export const findingsSuffix = '_findings' +export const findingsSuffix = '_qCodeScanFindings' From 9e58c93cf13f965b6d1c25b8c58a6054f887cab0 Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Fri, 20 Jun 2025 20:00:42 -0700 Subject: [PATCH 04/29] feat(amazonq): store the findings locally and include a path to them in ChatParams --- packages/amazonq/src/lsp/chat/messages.ts | 13 ++++++++ .../service/diagnosticsProvider.ts | 31 +++++++++++++++++++ .../service/securityIssueProvider.ts | 10 ++++++ .../core/src/shared/settings-toolkit.gen.ts | 4 +-- 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 0ff9f1785fc..b6f26f3a715 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -72,6 +72,7 @@ import { CodeWhispererSettings, initSecurityScanRender, ReferenceLogViewProvider, + SecurityIssueProvider, SecurityIssueTreeViewProvider, CodeWhispererConstants, } from 'aws-core-vscode/codewhisperer' @@ -89,6 +90,7 @@ import { decryptResponse, encryptRequest } from '../encryption' import { getCursorState } from '../utils' import { focusAmazonQPanel } from './commands' import { ChatMessage } from '@aws/language-server-runtimes/server-interface' +import path from 'path' export function registerActiveEditorChangeListener(languageClient: LanguageClient) { let debounceTimer: NodeJS.Timeout | undefined @@ -285,6 +287,17 @@ export function registerMessageListeners( if (editor) { chatParams.cursorState = getCursorState(editor.selections) chatParams.textDocument = { uri: editor.document.uri.toString() } + chatParams.findingsPath = path.join( + __dirname, + '..', + '..', + '..', + '..', + '..', + '..', + 'findings', + `SecurityIssues-${SecurityIssueProvider.instance.id}.json` + ) } const chatRequest = await encryptRequest(chatParams, encryptionKey) diff --git a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts index f181bdb146d..1877d2fd59d 100644 --- a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts +++ b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts @@ -8,6 +8,8 @@ import { CodeScanIssue, AggregatedCodeScanIssue } from '../models/model' import { CodeAnalysisScope, codewhispererDiagnosticSourceLabel } from '../models/constants' import { SecurityIssueTreeViewProvider } from './securityIssueTreeViewProvider' import { SecurityIssueProvider } from './securityIssueProvider' +import fs = require('fs') +import path from 'path' export interface SecurityDiagnostic extends vscode.Diagnostic { findingId?: string @@ -39,6 +41,15 @@ export function initSecurityScanRender( updateSecurityIssuesForProviders(securityRecommendation, scope === CodeAnalysisScope.FILE_AUTO) } securityScanRender.initialized = true + const issuesJson = JSON.stringify(SecurityIssueProvider.instance.issues) + const filePath = path.join(__dirname, '..', '..', '..', '..', '..', '..', 'findings') + fs.existsSync(filePath) || fs.mkdirSync(filePath) + fs.writeFileSync( + path.join(filePath, `SecurityIssues-${SecurityIssueProvider.instance.id}.json`), + issuesJson, + 'utf8' + ) + cleanOldFiles(filePath) } function updateSecurityIssuesForProviders(securityRecommendation: AggregatedCodeScanIssue, isAutoScope?: boolean) { @@ -56,6 +67,26 @@ function updateSecurityIssuesForProviders(securityRecommendation: AggregatedCode SecurityIssueTreeViewProvider.instance.refresh() } +function cleanOldFiles(dirPath: string, maxfiles = 100) { + const files = fs.readdirSync(dirPath) + if (files.length > maxfiles) { + type Stat = { fileName: string; mtime: number } + const stats: Stat[] = [] + for (const file of files) { + const stat = fs.statSync(path.join(dirPath, file[0])) + stats.push({ + fileName: file[0], + mtime: stat.mtime.getTime(), + }) + } + const sortedStats = stats.sort((a: Stat, b: Stat) => a.mtime - b.mtime) + const numberToDelete = files.length - maxfiles + for (let i = 0; i < numberToDelete; i++) { + fs.rmSync(path.join(dirPath, sortedStats[i].fileName)) + } + } +} + export function updateSecurityDiagnosticCollection(securityRecommendation: AggregatedCodeScanIssue) { const filePath = securityRecommendation.filePath const uri = vscode.Uri.file(filePath) diff --git a/packages/core/src/codewhisperer/service/securityIssueProvider.ts b/packages/core/src/codewhisperer/service/securityIssueProvider.ts index 61957e6eca5..8a1a5b6f688 100644 --- a/packages/core/src/codewhisperer/service/securityIssueProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueProvider.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode' import { AggregatedCodeScanIssue, CodeScanIssue, SuggestedFix } from '../models/model' +import { randomUUID } from '../../shared/crypto' export class SecurityIssueProvider { static #instance: SecurityIssueProvider public static get instance() { @@ -20,6 +21,15 @@ export class SecurityIssueProvider { this._issues = issues } + private _id: string = randomUUID() + public get id() { + return this._id + } + + public set id(id: string) { + this._id = id + } + public handleDocumentChange(event: vscode.TextDocumentChangeEvent) { // handleDocumentChange function may be triggered while testing by our own code generation. if (!event.contentChanges || event.contentChanges.length === 0) { diff --git a/packages/core/src/shared/settings-toolkit.gen.ts b/packages/core/src/shared/settings-toolkit.gen.ts index 10020cf51f9..819d6741f36 100644 --- a/packages/core/src/shared/settings-toolkit.gen.ts +++ b/packages/core/src/shared/settings-toolkit.gen.ts @@ -44,8 +44,8 @@ export const toolkitSettings = { "jsonResourceModification": {}, "amazonqLSP": {}, "amazonqLSPInline": {}, - "amazonqLSPInlineChat": {}, - "amazonqChatLSP": {} + "amazonqChatLSP": {}, + "amazonqLSPInlineChat": {} }, "aws.resources.enabledResources": {}, "aws.lambda.recentlyUploaded": {}, From 1f4cfc4aeff5f8f760f373d97aca29ed1a318b66 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Sat, 21 Jun 2025 19:36:22 -0700 Subject: [PATCH 05/29] fix: update q code review finding message suffix --- packages/core/src/codewhisperer/models/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index c02ba6ff8bc..04ee34a6b50 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -901,4 +901,4 @@ export const predictionTrackerDefaultConfig = { maxSupplementalContext: 15, } -export const findingsSuffix = '_qCodeScanFindings' +export const findingsSuffix = '_qCodeReviewFindings' From 2fc9f36b80ec6b05011127f25c78ecaa3a866edd Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Tue, 24 Jun 2025 09:59:38 -0700 Subject: [PATCH 06/29] fix(amazonq): change the findings suffix, adjust the stored findings when lines are modified. --- packages/core/src/codewhisperer/activation.ts | 1 + .../src/codewhisperer/models/constants.ts | 2 +- .../service/diagnosticsProvider.ts | 32 +------------------ .../service/securityIssueProvider.ts | 26 +++++++++++++++ 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index d6dd7fdc61d..c1fcfb2524b 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -476,6 +476,7 @@ export async function activate(context: ExtContext): Promise { CodeWhispererConstants.amazonqIgnoreNextLine ) ) + SecurityIssueProvider.instance.cleanOldFiles() }), vscode.window.createTreeView(SecurityIssueTreeViewProvider.viewType, { treeDataProvider: SecurityIssueTreeViewProvider.instance, diff --git a/packages/core/src/codewhisperer/models/constants.ts b/packages/core/src/codewhisperer/models/constants.ts index c02ba6ff8bc..04ee34a6b50 100644 --- a/packages/core/src/codewhisperer/models/constants.ts +++ b/packages/core/src/codewhisperer/models/constants.ts @@ -901,4 +901,4 @@ export const predictionTrackerDefaultConfig = { maxSupplementalContext: 15, } -export const findingsSuffix = '_qCodeScanFindings' +export const findingsSuffix = '_qCodeReviewFindings' diff --git a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts index 1877d2fd59d..b88899c4f37 100644 --- a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts +++ b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts @@ -8,8 +8,6 @@ import { CodeScanIssue, AggregatedCodeScanIssue } from '../models/model' import { CodeAnalysisScope, codewhispererDiagnosticSourceLabel } from '../models/constants' import { SecurityIssueTreeViewProvider } from './securityIssueTreeViewProvider' import { SecurityIssueProvider } from './securityIssueProvider' -import fs = require('fs') -import path from 'path' export interface SecurityDiagnostic extends vscode.Diagnostic { findingId?: string @@ -41,15 +39,7 @@ export function initSecurityScanRender( updateSecurityIssuesForProviders(securityRecommendation, scope === CodeAnalysisScope.FILE_AUTO) } securityScanRender.initialized = true - const issuesJson = JSON.stringify(SecurityIssueProvider.instance.issues) - const filePath = path.join(__dirname, '..', '..', '..', '..', '..', '..', 'findings') - fs.existsSync(filePath) || fs.mkdirSync(filePath) - fs.writeFileSync( - path.join(filePath, `SecurityIssues-${SecurityIssueProvider.instance.id}.json`), - issuesJson, - 'utf8' - ) - cleanOldFiles(filePath) + SecurityIssueProvider.instance.cleanOldFiles() } function updateSecurityIssuesForProviders(securityRecommendation: AggregatedCodeScanIssue, isAutoScope?: boolean) { @@ -67,26 +57,6 @@ function updateSecurityIssuesForProviders(securityRecommendation: AggregatedCode SecurityIssueTreeViewProvider.instance.refresh() } -function cleanOldFiles(dirPath: string, maxfiles = 100) { - const files = fs.readdirSync(dirPath) - if (files.length > maxfiles) { - type Stat = { fileName: string; mtime: number } - const stats: Stat[] = [] - for (const file of files) { - const stat = fs.statSync(path.join(dirPath, file[0])) - stats.push({ - fileName: file[0], - mtime: stat.mtime.getTime(), - }) - } - const sortedStats = stats.sort((a: Stat, b: Stat) => a.mtime - b.mtime) - const numberToDelete = files.length - maxfiles - for (let i = 0; i < numberToDelete; i++) { - fs.rmSync(path.join(dirPath, sortedStats[i].fileName)) - } - } -} - export function updateSecurityDiagnosticCollection(securityRecommendation: AggregatedCodeScanIssue) { const filePath = securityRecommendation.filePath const uri = vscode.Uri.file(filePath) diff --git a/packages/core/src/codewhisperer/service/securityIssueProvider.ts b/packages/core/src/codewhisperer/service/securityIssueProvider.ts index 8a1a5b6f688..4727aa43caa 100644 --- a/packages/core/src/codewhisperer/service/securityIssueProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueProvider.ts @@ -6,6 +6,8 @@ import * as vscode from 'vscode' import { AggregatedCodeScanIssue, CodeScanIssue, SuggestedFix } from '../models/model' import { randomUUID } from '../../shared/crypto' +import fs = require('fs') +import path from 'path' export class SecurityIssueProvider { static #instance: SecurityIssueProvider public static get instance() { @@ -167,4 +169,28 @@ export class SecurityIssueProvider { (i) => i.title === issue.title && i.startLine === issue.startLine && i.endLine === issue.endLine ) } + + public cleanOldFiles(maxfiles = 100): void { + const issuesJson = JSON.stringify(this._issues) + const dirPath = path.join(__dirname, '..', '..', '..', '..', '..', '..', 'findings') + fs.existsSync(dirPath) || fs.mkdirSync(dirPath) + fs.writeFileSync(path.join(dirPath, `SecurityIssues-${this._id}.json`), issuesJson, 'utf8') + const files = fs.readdirSync(dirPath) + if (files.length > maxfiles) { + type Stat = { fileName: string; mtime: number } + const stats: Stat[] = [] + for (const file of files) { + const stat = fs.statSync(path.join(dirPath, file[0])) + stats.push({ + fileName: file[0], + mtime: stat.mtime.getTime(), + }) + } + const sortedStats = stats.sort((a: Stat, b: Stat) => a.mtime - b.mtime) + const numberToDelete = files.length - maxfiles + for (let i = 0; i < numberToDelete; i++) { + fs.rmSync(path.join(dirPath, sortedStats[i].fileName)) + } + } + } } From e6d2f06941576f396c666068ca64da50f3970039 Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Wed, 25 Jun 2025 09:22:19 -0700 Subject: [PATCH 07/29] feat(amazonq): improve explain with q option for code issue --- packages/amazonq/src/lsp/chat/commands.ts | 59 ++++++++++++------- packages/amazonq/src/lsp/chat/messages.ts | 22 +++---- packages/core/src/codewhisperer/activation.ts | 2 +- .../codewhisperer/commands/basicCommands.ts | 2 +- .../service/diagnosticsProvider.ts | 2 +- .../securityIssueCodeActionProvider.ts | 2 +- .../service/securityIssueHoverProvider.ts | 52 ++++++++-------- 7 files changed, 81 insertions(+), 60 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index 115118a4ad2..f1ecdd88918 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -7,8 +7,9 @@ import { Commands, globals } from 'aws-core-vscode/shared' import { window } from 'vscode' import { AmazonQChatViewProvider } from './webviewProvider' import { CodeScanIssue } from 'aws-core-vscode/codewhisperer' -import { EditorContextExtractor } from 'aws-core-vscode/codewhispererChat' import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' +import * as vscode from 'vscode' +import * as path from 'path' /** * TODO: Re-enable these once we can figure out which path they're going to live in @@ -27,25 +28,35 @@ export function registerCommands(provider: AmazonQChatViewProvider) { type: 'chatMessage', }) }), - Commands.register('aws.amazonq.explainIssue', async (issue: CodeScanIssue) => { + Commands.register('aws.amazonq.explainIssue', async (issue: CodeScanIssue, filePath: string) => { void focusAmazonQPanel().then(async () => { - const editorContextExtractor = new EditorContextExtractor() - const extractedContext = await editorContextExtractor.extractContextForTrigger('ContextMenu') - const selectedCode = - extractedContext?.activeFileContext?.fileText - ?.split('\n') - .slice(issue.startLine, issue.endLine) - .join('\n') ?? '' - - // The message that gets sent to the UI - const uiMessage = [ - 'Explain the ', - issue.title, - ' issue in the following code:', - '\n```\n', - selectedCode, - '\n```', - ].join('') + let selectedCode: string | undefined = undefined + if (issue && filePath) { + const range = new vscode.Range(issue.startLine, 0, issue.endLine, 0) + await vscode.workspace.openTextDocument(filePath).then((doc) => { + void vscode.window.showTextDocument(doc, { + selection: range, + viewColumn: vscode.ViewColumn.One, + preview: true, + }) + selectedCode = stripIndent(doc.getText(range)) + }) + } + const uiMessageComponents = [`## ${issue.title}`] + if (filePath) { + uiMessageComponents.push(`File: ${path.basename(filePath)}`) + } + if (issue.relatedVulnerabilities !== undefined && issue.relatedVulnerabilities.length > 0) { + uiMessageComponents.push( + `Common Weakness Enumeration (CWE): ${issue.relatedVulnerabilities.map((cwe) => `[${cwe}](https://cwe.mitre.org/data/definitions/${cwe}.html)`).join(', ')}` + ) + } + if (issue.detectorName !== undefined) { + uiMessageComponents.push(`Detector Library: [${issue.detectorName}](${issue.recommendation.url})`) + } + if (selectedCode) { + uiMessageComponents.push(`\`\`\`\n${selectedCode}\n\`\`\``) + } // The message that gets sent to the backend const contextMessage = `Explain the issue "${issue.title}" (${JSON.stringify( @@ -58,7 +69,7 @@ export function registerCommands(provider: AmazonQChatViewProvider) { selection: '', triggerType: 'contextMenu', prompt: { - prompt: uiMessage, // what gets sent to the user + prompt: uiMessageComponents.join('\n'), // what gets sent to the user escapedPrompt: contextMessage, // what gets sent to the backend }, autoSubmit: true, @@ -119,6 +130,14 @@ function registerGenericCommand(commandName: string, genericCommand: string, pro }) } +function stripIndent(text: string): string { + const lines = text.split('\n') + const minIndent = Math.min( + ...lines.filter((line) => line.trim()).map((line) => line.length - line.trimStart().length) + ) + return lines.map((line) => line.slice(minIndent)).join('\n') +} + /** * Importing focusAmazonQPanel from aws-core-vscode/amazonq leads to several dependencies down the chain not resolving since AmazonQ chat * is currently only activated on node, but the language server is activated on both web and node. diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index b6f26f3a715..75a1bcda7ef 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -287,17 +287,17 @@ export function registerMessageListeners( if (editor) { chatParams.cursorState = getCursorState(editor.selections) chatParams.textDocument = { uri: editor.document.uri.toString() } - chatParams.findingsPath = path.join( - __dirname, - '..', - '..', - '..', - '..', - '..', - '..', - 'findings', - `SecurityIssues-${SecurityIssueProvider.instance.id}.json` - ) + // chatParams.findingsPath = path.join( + // __dirname, + // '..', + // '..', + // '..', + // '..', + // '..', + // '..', + // 'findings', + // `SecurityIssues-${SecurityIssueProvider.instance.id}.json` + // ) } const chatRequest = await encryptRequest(chatParams, encryptionKey) diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index c1fcfb2524b..11bbce5a817 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -476,7 +476,7 @@ export async function activate(context: ExtContext): Promise { CodeWhispererConstants.amazonqIgnoreNextLine ) ) - SecurityIssueProvider.instance.cleanOldFiles() + // SecurityIssueProvider.instance.cleanOldFiles() }), vscode.window.createTreeView(SecurityIssueTreeViewProvider.viewType, { treeDataProvider: SecurityIssueTreeViewProvider.instance, diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts index f2b67c49593..2bfbfb05299 100644 --- a/packages/core/src/codewhisperer/commands/basicCommands.ts +++ b/packages/core/src/codewhisperer/commands/basicCommands.ts @@ -836,7 +836,7 @@ export const regenerateFix = Commands.declare( export const explainIssue = Commands.declare( { id: 'aws.amazonq.security.explain' }, () => async (issueItem: IssueItem) => { - await vscode.commands.executeCommand('aws.amazonq.explainIssue', issueItem.issue) + await vscode.commands.executeCommand('aws.amazonq.explainIssue', issueItem.issue, issueItem.filePath) } ) diff --git a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts index b88899c4f37..e110f37c121 100644 --- a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts +++ b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts @@ -39,7 +39,7 @@ export function initSecurityScanRender( updateSecurityIssuesForProviders(securityRecommendation, scope === CodeAnalysisScope.FILE_AUTO) } securityScanRender.initialized = true - SecurityIssueProvider.instance.cleanOldFiles() + // SecurityIssueProvider.instance.cleanOldFiles() } function updateSecurityIssuesForProviders(securityRecommendation: AggregatedCodeScanIssue, isAutoScope?: boolean) { diff --git a/packages/core/src/codewhisperer/service/securityIssueCodeActionProvider.ts b/packages/core/src/codewhisperer/service/securityIssueCodeActionProvider.ts index f1d01494d54..4dc7bceebe7 100644 --- a/packages/core/src/codewhisperer/service/securityIssueCodeActionProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueCodeActionProvider.ts @@ -66,7 +66,7 @@ export class SecurityIssueCodeActionProvider implements vscode.CodeActionProvide `Amazon Q: Explain "${issue.title}"`, vscode.CodeActionKind.QuickFix ) - const explainWithQArgs = [issue] + const explainWithQArgs = [issue, group.filePath] explainWithQ.command = { title: 'Explain with Amazon Q', command: 'aws.amazonq.explainIssue', diff --git a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts index b82c10063e6..7d350965673 100644 --- a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts @@ -79,23 +79,24 @@ export class SecurityIssueHoverProvider implements vscode.HoverProvider { `${suggestedFix?.code && suggestedFix.description !== '' ? suggestedFix.description : issue.recommendation.text}\n\n` ) - const viewDetailsCommand = this._getCommandMarkdown( - 'aws.amazonq.openSecurityIssuePanel', - [issue, filePath], - 'eye', - 'View Details', - `Open "${amazonqCodeIssueDetailsTabTitle}"` - ) - markdownString.appendMarkdown(viewDetailsCommand) + // Commenting out view details button as we are deprecating the Code Issue Details page + // const viewDetailsCommand = this._getCommandMarkdown( + // 'aws.amazonq.openSecurityIssuePanel', + // [issue, filePath], + // 'eye', + // 'View Details', + // `Open "${amazonqCodeIssueDetailsTabTitle}"` + // ) + // markdownString.appendMarkdown(viewDetailsCommand) const explainWithQCommand = this._getCommandMarkdown( 'aws.amazonq.explainIssue', - [issue], + [issue, filePath], 'comment', 'Explain', 'Explain with Amazon Q' ) - markdownString.appendMarkdown(' | ' + explainWithQCommand) + markdownString.appendMarkdown(explainWithQCommand) const ignoreIssueCommand = this._getCommandMarkdown( 'aws.amazonq.security.ignore', @@ -115,21 +116,22 @@ export class SecurityIssueHoverProvider implements vscode.HoverProvider { ) markdownString.appendMarkdown(' | ' + ignoreSimilarIssuesCommand) - if (suggestedFix && suggestedFix.code) { - const applyFixCommand = this._getCommandMarkdown( - 'aws.amazonq.applySecurityFix', - [issue, filePath, 'hover'], - 'wrench', - 'Fix', - 'Fix with Amazon Q' - ) - markdownString.appendMarkdown(' | ' + applyFixCommand) - - markdownString.appendMarkdown('### Suggested Fix Preview\n') - markdownString.appendMarkdown( - `${this._makeCodeBlock(suggestedFix.code, issue.detectorId.split('/').shift())}\n` - ) - } + // Commenting out fix related options as we are deprecating Code Issue Details page + // if (suggestedFix && suggestedFix.code) { + // const applyFixCommand = this._getCommandMarkdown( + // 'aws.amazonq.applySecurityFix', + // [issue, filePath, 'hover'], + // 'wrench', + // 'Fix', + // 'Fix with Amazon Q' + // ) + // markdownString.appendMarkdown(' | ' + applyFixCommand) + + // markdownString.appendMarkdown('### Suggested Fix Preview\n') + // markdownString.appendMarkdown( + // `${this._makeCodeBlock(suggestedFix.code, issue.detectorId.split('/').shift())}\n` + // ) + // } return markdownString } From 865b0e3ecbf3e608dab79a243568db0213a56f16 Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Wed, 25 Jun 2025 09:49:59 -0700 Subject: [PATCH 08/29] feat(amazonq): update findings bar --- packages/amazonq/package.json | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 8f398613ffb..2f225ec1a86 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -442,6 +442,11 @@ }, { "command": "aws.amazonq.openSecurityIssuePanel", + "when": "false && view == aws.amazonq.SecurityIssuesTree && (viewItem == issueWithoutFix || viewItem == issueWithFix || viewItem == issueWithFixDisabled)", + "group": "inline@4" + }, + { + "command": "aws.amazonq.security.explain", "when": "view == aws.amazonq.SecurityIssuesTree && (viewItem == issueWithoutFix || viewItem == issueWithFix || viewItem == issueWithFixDisabled)", "group": "inline@4" }, @@ -452,7 +457,7 @@ }, { "command": "aws.amazonq.security.generateFix", - "when": "view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithoutFix", + "when": "false && view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithoutFix", "group": "inline@6" }, { @@ -535,16 +540,17 @@ "aws.amazonq.submenu.securityIssueMoreActions": [ { "command": "aws.amazonq.security.explain", + "when": "false", "group": "1_more@1" }, { "command": "aws.amazonq.applySecurityFix", - "when": "view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithFix", + "when": "false && view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithFix", "group": "1_more@3" }, { "command": "aws.amazonq.security.regenerateFix", - "when": "view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithFix", + "when": "false && view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithFix", "group": "1_more@4" }, { @@ -778,6 +784,7 @@ { "command": "aws.amazonq.security.explain", "title": "%AWS.command.amazonq.explainIssue%", + "icon": "$(search)", "enablement": "view == aws.amazonq.SecurityIssuesTree" }, { From 5b7df1683c895c153cf09f188bb2ed54aafcf0e4 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Wed, 25 Jun 2025 10:52:18 -0700 Subject: [PATCH 09/29] fix: remove unused code --- packages/amazonq/src/lsp/chat/messages.ts | 2 - .../service/securityIssueHoverProvider.ts | 84 ------------------- 2 files changed, 86 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 6b3da4a86d9..05c77ab812a 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -72,7 +72,6 @@ import { CodeWhispererSettings, initSecurityScanRender, ReferenceLogViewProvider, - SecurityIssueProvider, SecurityIssueTreeViewProvider, CodeWhispererConstants, } from 'aws-core-vscode/codewhisperer' @@ -90,7 +89,6 @@ import { decryptResponse, encryptRequest } from '../encryption' import { getCursorState } from '../utils' import { focusAmazonQPanel } from './commands' import { ChatMessage } from '@aws/language-server-runtimes/server-interface' -import path from 'path' export function registerActiveEditorChangeListener(languageClient: LanguageClient) { let debounceTimer: NodeJS.Timeout | undefined diff --git a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts index 7d350965673..3acabeff695 100644 --- a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts @@ -10,7 +10,6 @@ import path from 'path' import { AuthUtil } from '../util/authUtil' import { TelemetryHelper } from '../util/telemetryHelper' import { SecurityIssueProvider } from './securityIssueProvider' -import { amazonqCodeIssueDetailsTabTitle } from '../models/constants' export class SecurityIssueHoverProvider implements vscode.HoverProvider { static #instance: SecurityIssueHoverProvider @@ -79,16 +78,6 @@ export class SecurityIssueHoverProvider implements vscode.HoverProvider { `${suggestedFix?.code && suggestedFix.description !== '' ? suggestedFix.description : issue.recommendation.text}\n\n` ) - // Commenting out view details button as we are deprecating the Code Issue Details page - // const viewDetailsCommand = this._getCommandMarkdown( - // 'aws.amazonq.openSecurityIssuePanel', - // [issue, filePath], - // 'eye', - // 'View Details', - // `Open "${amazonqCodeIssueDetailsTabTitle}"` - // ) - // markdownString.appendMarkdown(viewDetailsCommand) - const explainWithQCommand = this._getCommandMarkdown( 'aws.amazonq.explainIssue', [issue, filePath], @@ -116,23 +105,6 @@ export class SecurityIssueHoverProvider implements vscode.HoverProvider { ) markdownString.appendMarkdown(' | ' + ignoreSimilarIssuesCommand) - // Commenting out fix related options as we are deprecating Code Issue Details page - // if (suggestedFix && suggestedFix.code) { - // const applyFixCommand = this._getCommandMarkdown( - // 'aws.amazonq.applySecurityFix', - // [issue, filePath, 'hover'], - // 'wrench', - // 'Fix', - // 'Fix with Amazon Q' - // ) - // markdownString.appendMarkdown(' | ' + applyFixCommand) - - // markdownString.appendMarkdown('### Suggested Fix Preview\n') - // markdownString.appendMarkdown( - // `${this._makeCodeBlock(suggestedFix.code, issue.detectorId.split('/').shift())}\n` - // ) - // } - return markdownString } @@ -147,60 +119,4 @@ export class SecurityIssueHoverProvider implements vscode.HoverProvider { } return `![${severity}](severity-${severity.toLowerCase()}.svg)` } - - /** - * Creates a markdown string to render a code diff block for a given code block. Lines - * that are highlighted red indicate deletion while lines highlighted in green indicate - * addition. An optional language can be provided for syntax highlighting on lines which are - * not additions or deletions. - * - * @param code The code containing the diff - * @param language The language for syntax highlighting - * @returns The markdown string - */ - private _makeCodeBlock(code: string, language?: string) { - const lines = code - .replaceAll('\n\\ No newline at end of file', '') - .replaceAll('--- buggyCode\n', '') - .replaceAll('+++ fixCode\n', '') - .split('\n') - const maxLineChars = lines.reduce((acc, curr) => Math.max(acc, curr.length), 0) - const paddedLines = lines.map((line) => line.padEnd(maxLineChars + 2)) - - // Group the lines into sections so consecutive lines of the same type can be placed in - // the same span below - const sections = [paddedLines[0]] - let i = 1 - while (i < paddedLines.length) { - if (paddedLines[i][0] === sections[sections.length - 1][0]) { - sections[sections.length - 1] += '\n' + paddedLines[i] - } else { - sections.push(paddedLines[i]) - } - i++ - } - - // Return each section with the correct syntax highlighting and background color - return sections - .map( - (section) => ` - - -\`\`\`${section.startsWith('-') || section.startsWith('+') ? 'diff' : section.startsWith('@@') ? undefined : language} -${section} -\`\`\` - - -` - ) - .join('
') - } } From 98b12654382530c5e010e724cabd5ff5d0497b43 Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Fri, 27 Jun 2025 10:08:45 -0700 Subject: [PATCH 10/29] fix(amazonq): commenting out unncalled functions, unused imports --- packages/amazonq/src/lsp/chat/messages.ts | 4 ++-- .../src/codewhisperer/service/securityIssueHoverProvider.ts | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 75a1bcda7ef..4aafd5cb771 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -72,7 +72,7 @@ import { CodeWhispererSettings, initSecurityScanRender, ReferenceLogViewProvider, - SecurityIssueProvider, + // SecurityIssueProvider, SecurityIssueTreeViewProvider, CodeWhispererConstants, } from 'aws-core-vscode/codewhisperer' @@ -90,7 +90,7 @@ import { decryptResponse, encryptRequest } from '../encryption' import { getCursorState } from '../utils' import { focusAmazonQPanel } from './commands' import { ChatMessage } from '@aws/language-server-runtimes/server-interface' -import path from 'path' +// import path from 'path' export function registerActiveEditorChangeListener(languageClient: LanguageClient) { let debounceTimer: NodeJS.Timeout | undefined diff --git a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts index 7d350965673..f61cea35fd3 100644 --- a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts @@ -10,7 +10,7 @@ import path from 'path' import { AuthUtil } from '../util/authUtil' import { TelemetryHelper } from '../util/telemetryHelper' import { SecurityIssueProvider } from './securityIssueProvider' -import { amazonqCodeIssueDetailsTabTitle } from '../models/constants' +// import { amazonqCodeIssueDetailsTabTitle } from '../models/constants' export class SecurityIssueHoverProvider implements vscode.HoverProvider { static #instance: SecurityIssueHoverProvider @@ -158,6 +158,8 @@ export class SecurityIssueHoverProvider implements vscode.HoverProvider { * @param language The language for syntax highlighting * @returns The markdown string */ + + /* private _makeCodeBlock(code: string, language?: string) { const lines = code .replaceAll('\n\\ No newline at end of file', '') @@ -203,4 +205,5 @@ ${section} ) .join('
') } + */ } From daa3ffe7bfa0b72c43544f0a92e4530fe390bffb Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Tue, 1 Jul 2025 22:11:25 -0700 Subject: [PATCH 11/29] Updated explain code issue prompt --- packages/amazonq/src/lsp/chat/commands.ts | 33 +++-------------------- packages/amazonq/src/lsp/chat/messages.ts | 11 -------- 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index f1ecdd88918..19e56c74356 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -30,7 +30,6 @@ export function registerCommands(provider: AmazonQChatViewProvider) { }), Commands.register('aws.amazonq.explainIssue', async (issue: CodeScanIssue, filePath: string) => { void focusAmazonQPanel().then(async () => { - let selectedCode: string | undefined = undefined if (issue && filePath) { const range = new vscode.Range(issue.startLine, 0, issue.endLine, 0) await vscode.workspace.openTextDocument(filePath).then((doc) => { @@ -39,29 +38,13 @@ export function registerCommands(provider: AmazonQChatViewProvider) { viewColumn: vscode.ViewColumn.One, preview: true, }) - selectedCode = stripIndent(doc.getText(range)) }) } - const uiMessageComponents = [`## ${issue.title}`] - if (filePath) { - uiMessageComponents.push(`File: ${path.basename(filePath)}`) - } - if (issue.relatedVulnerabilities !== undefined && issue.relatedVulnerabilities.length > 0) { - uiMessageComponents.push( - `Common Weakness Enumeration (CWE): ${issue.relatedVulnerabilities.map((cwe) => `[${cwe}](https://cwe.mitre.org/data/definitions/${cwe}.html)`).join(', ')}` - ) - } - if (issue.detectorName !== undefined) { - uiMessageComponents.push(`Detector Library: [${issue.detectorName}](${issue.recommendation.url})`) - } - if (selectedCode) { - uiMessageComponents.push(`\`\`\`\n${selectedCode}\n\`\`\``) - } + + const visibleMessageInChat = `_Explain **${issue.title}** issue in **${path.basename(filePath)}** at \`(${issue.startLine}, ${issue.endLine})\`_` // The message that gets sent to the backend - const contextMessage = `Explain the issue "${issue.title}" (${JSON.stringify( - issue - )}) and generate code demonstrating the fix` + const contextMessage = `Provide a small description of the issue followed by a small description of the recommended fix for it. Code issue - ${JSON.stringify(issue)}` void provider.webview?.postMessage({ command: 'sendToPrompt', @@ -69,7 +52,7 @@ export function registerCommands(provider: AmazonQChatViewProvider) { selection: '', triggerType: 'contextMenu', prompt: { - prompt: uiMessageComponents.join('\n'), // what gets sent to the user + prompt: visibleMessageInChat, // what gets sent to the user escapedPrompt: contextMessage, // what gets sent to the backend }, autoSubmit: true, @@ -130,14 +113,6 @@ function registerGenericCommand(commandName: string, genericCommand: string, pro }) } -function stripIndent(text: string): string { - const lines = text.split('\n') - const minIndent = Math.min( - ...lines.filter((line) => line.trim()).map((line) => line.length - line.trimStart().length) - ) - return lines.map((line) => line.slice(minIndent)).join('\n') -} - /** * Importing focusAmazonQPanel from aws-core-vscode/amazonq leads to several dependencies down the chain not resolving since AmazonQ chat * is currently only activated on node, but the language server is activated on both web and node. diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 05c77ab812a..f0a49292d6f 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -286,17 +286,6 @@ export function registerMessageListeners( if (editor) { chatParams.cursorState = getCursorState(editor.selections) chatParams.textDocument = { uri: editor.document.uri.toString() } - // chatParams.findingsPath = path.join( - // __dirname, - // '..', - // '..', - // '..', - // '..', - // '..', - // '..', - // 'findings', - // `SecurityIssues-${SecurityIssueProvider.instance.id}.json` - // ) } const chatRequest = await encryptRequest(chatParams, encryptionKey) From 784ae63556c55f2066fb03447ed74dd363f0976e Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Thu, 3 Jul 2025 12:32:55 -0700 Subject: [PATCH 12/29] feat(amazonq): separate the Explain and Generate Fix commands --- packages/amazonq/package.json | 26 +- packages/amazonq/src/lsp/chat/commands.ts | 34 ++- .../codewhisperer/commands/basicCommands.ts | 231 +++++++++--------- .../service/securityIssueHoverProvider.ts | 9 + .../securityIssue/securityIssueWebview.ts | 5 +- 5 files changed, 181 insertions(+), 124 deletions(-) diff --git a/packages/amazonq/package.json b/packages/amazonq/package.json index 9ad47d29521..263e5465a34 100644 --- a/packages/amazonq/package.json +++ b/packages/amazonq/package.json @@ -451,13 +451,13 @@ "group": "inline@4" }, { - "command": "aws.amazonq.security.ignore", + "command": "aws.amazonq.security.generateFix", "when": "view == aws.amazonq.SecurityIssuesTree && (viewItem == issueWithoutFix || viewItem == issueWithFix || viewItem == issueWithFixDisabled)", "group": "inline@5" }, { - "command": "aws.amazonq.security.generateFix", - "when": "false && view == aws.amazonq.SecurityIssuesTree && viewItem == issueWithoutFix", + "command": "aws.amazonq.security.ignore", + "when": "view == aws.amazonq.SecurityIssuesTree && (viewItem == issueWithoutFix || viewItem == issueWithFix || viewItem == issueWithFixDisabled)", "group": "inline@6" }, { @@ -1332,26 +1332,40 @@ "fontCharacter": "\\f1de" } }, - "aws-schemas-registry": { + "aws-sagemaker-code-editor": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1df" } }, - "aws-schemas-schema": { + "aws-sagemaker-jupyter-lab": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1e0" } }, - "aws-stepfunctions-preview": { + "aws-schemas-registry": { "description": "AWS Contributed Icon", "default": { "fontPath": "./resources/fonts/aws-toolkit-icons.woff", "fontCharacter": "\\f1e1" } + }, + "aws-schemas-schema": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1e2" + } + }, + "aws-stepfunctions-preview": { + "description": "AWS Contributed Icon", + "default": { + "fontPath": "./resources/fonts/aws-toolkit-icons.woff", + "fontCharacter": "\\f1e3" + } } }, "walkthroughs": [ diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index 19e56c74356..ffb0a634220 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -44,7 +44,39 @@ export function registerCommands(provider: AmazonQChatViewProvider) { const visibleMessageInChat = `_Explain **${issue.title}** issue in **${path.basename(filePath)}** at \`(${issue.startLine}, ${issue.endLine})\`_` // The message that gets sent to the backend - const contextMessage = `Provide a small description of the issue followed by a small description of the recommended fix for it. Code issue - ${JSON.stringify(issue)}` + const contextMessage = `Provide a small description of the issue. Do not attempt to fix the issue, only explain it. Code issue - ${JSON.stringify(issue)}` + + void provider.webview?.postMessage({ + command: 'sendToPrompt', + params: { + selection: '', + triggerType: 'contextMenu', + prompt: { + prompt: visibleMessageInChat, // what gets sent to the user + escapedPrompt: contextMessage, // what gets sent to the backend + }, + autoSubmit: true, + }, + }) + }) + }), + Commands.register('aws.amazonq.generateFix', async (issue: CodeScanIssue, filePath: string) => { + void focusAmazonQPanel().then(async () => { + if (issue && filePath) { + const range = new vscode.Range(issue.startLine, 0, issue.endLine, 0) + await vscode.workspace.openTextDocument(filePath).then((doc) => { + void vscode.window.showTextDocument(doc, { + selection: range, + viewColumn: vscode.ViewColumn.One, + preview: true, + }) + }) + } + + const visibleMessageInChat = `_Fix **${issue.title}** issue in **${path.basename(filePath)}** at \`(${issue.startLine}, ${issue.endLine})\`_` + + // The message that gets sent to the backend + const contextMessage = `Generate a fix for the following code issue. Do not explain the issue, just generate and explain the fix. The user should have the option to accept or reject the fix before any code is changed. Code issue - ${JSON.stringify(issue)}` void provider.webview?.postMessage({ command: 'sendToPrompt', diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts index 2bfbfb05299..1bdd2454342 100644 --- a/packages/core/src/codewhisperer/commands/basicCommands.ts +++ b/packages/core/src/codewhisperer/commands/basicCommands.ts @@ -12,7 +12,6 @@ import { DefaultCodeWhispererClient } from '../client/codewhisperer' import { confirmStopSecurityScan, startSecurityScan } from './startSecurityScan' import { SecurityPanelViewProvider } from '../views/securityPanelViewProvider' import { - codeFixState, CodeScanIssue, CodeScansState, codeScanState, @@ -50,7 +49,7 @@ import { once } from '../../shared/utilities/functionUtils' import { focusAmazonQPanel } from '../../codewhispererChat/commands/registerCommands' import { removeDiagnostic } from '../service/diagnosticsProvider' import { SsoAccessTokenProvider } from '../../auth/sso/ssoAccessTokenProvider' -import { ToolkitError, getErrorMsg, getTelemetryReason, getTelemetryReasonDesc } from '../../shared/errors' +import { ToolkitError, getTelemetryReason, getTelemetryReasonDesc } from '../../shared/errors' import { isRemoteWorkspace } from '../../shared/vscode/env' import { isBuilderIdConnection } from '../../auth/connection' import globals from '../../shared/extensionGlobals' @@ -61,7 +60,6 @@ import { SecurityIssueProvider } from '../service/securityIssueProvider' import { CodeWhispererSettings } from '../util/codewhispererSettings' import { closeDiff, getPatchedCode } from '../../shared/utilities/diffUtils' import { insertCommentAboveLine } from '../../shared/utilities/commentUtils' -import { startCodeFixGeneration } from './startCodeFixGeneration' import { DefaultAmazonQAppInitContext } from '../../amazonq/apps/initContext' import path from 'path' import { UserWrittenCodeTracker } from '../tracker/userWrittenCodeTracker' @@ -369,9 +367,9 @@ export const openSecurityIssuePanel = Commands.declare( const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath await showSecurityIssueWebview(context.extensionContext, targetIssue, targetFilePath) - if (targetIssue.suggestedFixes.length === 0) { - await generateFix.execute(targetIssue, targetFilePath, 'webview', true, false) - } + // if (targetIssue.suggestedFixes.length === 0) { + // await generateFix.execute(targetIssue, targetFilePath, 'webview', true, false) + // } telemetry.codewhisperer_codeScanIssueViewDetails.emit({ findingId: targetIssue.findingId, detectorId: targetIssue.detectorId, @@ -685,116 +683,117 @@ export const generateFix = Commands.declare( { id: 'aws.amazonq.security.generateFix' }, (client: DefaultCodeWhispererClient, context: ExtContext) => async ( - issue: CodeScanIssue | IssueItem | undefined, + issueItem: IssueItem, filePath: string, source: Component, refresh: boolean = false, shouldOpenSecurityIssuePanel: boolean = true ) => { - const targetIssue: CodeScanIssue | undefined = issue instanceof IssueItem ? issue.issue : issue - const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath - const targetSource: Component = issue instanceof IssueItem ? 'tree' : source - if (!targetIssue) { - return - } - if (targetIssue.ruleId === CodeWhispererConstants.sasRuleId) { - getLogger().warn('GenerateFix is not available for SAS findings.') - return - } - await telemetry.codewhisperer_codeScanIssueGenerateFix.run(async () => { - try { - if (shouldOpenSecurityIssuePanel) { - await vscode.commands - .executeCommand('aws.amazonq.openSecurityIssuePanel', targetIssue, targetFilePath) - .then(undefined, (e) => { - getLogger().error('Failed to open security issue panel: %s', e.message) - }) - } - await updateSecurityIssueWebview({ - isGenerateFixLoading: true, - // eslint-disable-next-line unicorn/no-null - generateFixError: null, - context: context.extensionContext, - filePath: targetFilePath, - shouldRefreshView: false, - }) - - codeFixState.setToRunning() - let hasSuggestedFix = false - const { suggestedFix, jobId } = await startCodeFixGeneration( - client, - targetIssue, - targetFilePath, - targetIssue.findingId - ) - // redact the fix if the user disabled references and there is a reference - if ( - // TODO: enable references later for scans - // !CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled() && - suggestedFix?.references && - suggestedFix?.references?.length > 0 - ) { - getLogger().debug( - `Received fix with reference and user settings disallow references. Job ID: ${jobId}` - ) - // TODO: re-enable notifications once references published - // void vscode.window.showInformationMessage( - // 'Your settings do not allow code generation with references.' - // ) - hasSuggestedFix = false - } else { - hasSuggestedFix = suggestedFix !== undefined - } - telemetry.record({ includesFix: hasSuggestedFix }) - const updatedIssue: CodeScanIssue = { - ...targetIssue, - fixJobId: jobId, - suggestedFixes: - hasSuggestedFix && suggestedFix - ? [ - { - code: suggestedFix.codeDiff, - description: suggestedFix.description ?? '', - references: suggestedFix.references, - }, - ] - : [], - } - await updateSecurityIssueWebview({ - issue: updatedIssue, - isGenerateFixLoading: false, - filePath: targetFilePath, - context: context.extensionContext, - shouldRefreshView: true, - }) - - SecurityIssueProvider.instance.updateIssue(updatedIssue, targetFilePath) - SecurityIssueTreeViewProvider.instance.refresh() - } catch (err) { - const error = err instanceof Error ? err : new TypeError('Unexpected error') - await updateSecurityIssueWebview({ - issue: targetIssue, - isGenerateFixLoading: false, - generateFixError: getErrorMsg(error, true), - filePath: targetFilePath, - context: context.extensionContext, - shouldRefreshView: false, - }) - SecurityIssueProvider.instance.updateIssue(targetIssue, targetFilePath) - SecurityIssueTreeViewProvider.instance.refresh() - throw err - } finally { - telemetry.record({ - component: targetSource, - detectorId: targetIssue.detectorId, - findingId: targetIssue.findingId, - ruleId: targetIssue.ruleId, - variant: refresh ? 'refresh' : undefined, - autoDetected: targetIssue.autoDetected, - codewhispererCodeScanJobId: targetIssue.scanJobId, - }) - } - }) + await vscode.commands.executeCommand('aws.amazonq.generateFix', issueItem.issue, issueItem.filePath) + // const targetIssue: CodeScanIssue | undefined = issue instanceof IssueItem ? issue.issue : issue + // const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath + // const targetSource: Component = issue instanceof IssueItem ? 'tree' : source + // if (!targetIssue) { + // return + // } + // if (targetIssue.ruleId === CodeWhispererConstants.sasRuleId) { + // getLogger().warn('GenerateFix is not available for SAS findings.') + // return + // } + // await telemetry.codewhisperer_codeScanIssueGenerateFix.run(async () => { + // try { + // if (shouldOpenSecurityIssuePanel) { + // await vscode.commands + // .executeCommand('aws.amazonq.openSecurityIssuePanel', targetIssue, targetFilePath) + // .then(undefined, (e) => { + // getLogger().error('Failed to open security issue panel: %s', e.message) + // }) + // } + // await updateSecurityIssueWebview({ + // isGenerateFixLoading: true, + // // eslint-disable-next-line unicorn/no-null + // generateFixError: null, + // context: context.extensionContext, + // filePath: targetFilePath, + // shouldRefreshView: false, + // }) + + // codeFixState.setToRunning() + // let hasSuggestedFix = false + // const { suggestedFix, jobId } = await startCodeFixGeneration( + // client, + // targetIssue, + // targetFilePath, + // targetIssue.findingId + // ) + // // redact the fix if the user disabled references and there is a reference + // if ( + // // TODO: enable references later for scans + // // !CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled() && + // suggestedFix?.references && + // suggestedFix?.references?.length > 0 + // ) { + // getLogger().debug( + // `Received fix with reference and user settings disallow references. Job ID: ${jobId}` + // ) + // // TODO: re-enable notifications once references published + // // void vscode.window.showInformationMessage( + // // 'Your settings do not allow code generation with references.' + // // ) + // hasSuggestedFix = false + // } else { + // hasSuggestedFix = suggestedFix !== undefined + // } + // telemetry.record({ includesFix: hasSuggestedFix }) + // const updatedIssue: CodeScanIssue = { + // ...targetIssue, + // fixJobId: jobId, + // suggestedFixes: + // hasSuggestedFix && suggestedFix + // ? [ + // { + // code: suggestedFix.codeDiff, + // description: suggestedFix.description ?? '', + // references: suggestedFix.references, + // }, + // ] + // : [], + // } + // await updateSecurityIssueWebview({ + // issue: updatedIssue, + // isGenerateFixLoading: false, + // filePath: targetFilePath, + // context: context.extensionContext, + // shouldRefreshView: true, + // }) + + // SecurityIssueProvider.instance.updateIssue(updatedIssue, targetFilePath) + // SecurityIssueTreeViewProvider.instance.refresh() + // } catch (err) { + // const error = err instanceof Error ? err : new TypeError('Unexpected error') + // await updateSecurityIssueWebview({ + // issue: targetIssue, + // isGenerateFixLoading: false, + // generateFixError: getErrorMsg(error, true), + // filePath: targetFilePath, + // context: context.extensionContext, + // shouldRefreshView: false, + // }) + // SecurityIssueProvider.instance.updateIssue(targetIssue, targetFilePath) + // SecurityIssueTreeViewProvider.instance.refresh() + // throw err + // } finally { + // telemetry.record({ + // component: targetSource, + // detectorId: targetIssue.detectorId, + // findingId: targetIssue.findingId, + // ruleId: targetIssue.ruleId, + // variant: refresh ? 'refresh' : undefined, + // autoDetected: targetIssue.autoDetected, + // codewhispererCodeScanJobId: targetIssue.scanJobId, + // }) + // } + // }) } ) @@ -825,11 +824,11 @@ export const rejectFix = Commands.declare( export const regenerateFix = Commands.declare( { id: 'aws.amazonq.security.regenerateFix' }, () => async (issue: CodeScanIssue | IssueItem | undefined, filePath: string, source: Component) => { - const targetIssue: CodeScanIssue | undefined = issue instanceof IssueItem ? issue.issue : issue - const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath - const targetSource: Component = issue instanceof IssueItem ? 'tree' : source - const updatedIssue = await rejectFix.execute(targetIssue, targetFilePath) - await generateFix.execute(updatedIssue, targetFilePath, targetSource, true) + // const targetIssue: CodeScanIssue | undefined = issue instanceof IssueItem ? issue.issue : issue + // const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath + // const targetSource: Component = issue instanceof IssueItem ? 'tree' : source + // const updatedIssue = await rejectFix.execute(targetIssue, targetFilePath) + // await generateFix.execute(updatedIssue, targetFilePath, targetSource, true) } ) diff --git a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts index 3acabeff695..95ea3869cb0 100644 --- a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts @@ -87,6 +87,15 @@ export class SecurityIssueHoverProvider implements vscode.HoverProvider { ) markdownString.appendMarkdown(explainWithQCommand) + const generateFixCommand = this._getCommandMarkdown( + 'aws.amazonq.generateFix', + [issue, filePath], + 'comment', + 'Fix', + 'Generate Fix for Issue' + ) + markdownString.appendMarkdown(' | ' + generateFixCommand) + const ignoreIssueCommand = this._getCommandMarkdown( 'aws.amazonq.security.ignore', [issue, filePath, 'hover'], diff --git a/packages/core/src/codewhisperer/views/securityIssue/securityIssueWebview.ts b/packages/core/src/codewhisperer/views/securityIssue/securityIssueWebview.ts index d511bd9a5f6..632283215ab 100644 --- a/packages/core/src/codewhisperer/views/securityIssue/securityIssueWebview.ts +++ b/packages/core/src/codewhisperer/views/securityIssue/securityIssueWebview.ts @@ -109,7 +109,10 @@ export class SecurityIssueWebview extends VueWebview { } public generateFix() { - void vscode.commands.executeCommand('aws.amazonq.security.generateFix', this.issue, this.filePath, 'webview') + const args = [this.issue] + void this.navigateToFile()?.then(() => { + void vscode.commands.executeCommand('aws.amazonq.generateFix', ...args) + }) } public regenerateFix() { From ec7048ea805c10ac5e2f0400cba135f1e1fc8ef4 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Fri, 4 Jul 2025 14:17:29 -0700 Subject: [PATCH 13/29] fix: remove commented code --- .../codewhisperer/commands/basicCommands.ts | 104 ------------------ 1 file changed, 104 deletions(-) diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts index 1bdd2454342..c7df25bf824 100644 --- a/packages/core/src/codewhisperer/commands/basicCommands.ts +++ b/packages/core/src/codewhisperer/commands/basicCommands.ts @@ -690,110 +690,6 @@ export const generateFix = Commands.declare( shouldOpenSecurityIssuePanel: boolean = true ) => { await vscode.commands.executeCommand('aws.amazonq.generateFix', issueItem.issue, issueItem.filePath) - // const targetIssue: CodeScanIssue | undefined = issue instanceof IssueItem ? issue.issue : issue - // const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath - // const targetSource: Component = issue instanceof IssueItem ? 'tree' : source - // if (!targetIssue) { - // return - // } - // if (targetIssue.ruleId === CodeWhispererConstants.sasRuleId) { - // getLogger().warn('GenerateFix is not available for SAS findings.') - // return - // } - // await telemetry.codewhisperer_codeScanIssueGenerateFix.run(async () => { - // try { - // if (shouldOpenSecurityIssuePanel) { - // await vscode.commands - // .executeCommand('aws.amazonq.openSecurityIssuePanel', targetIssue, targetFilePath) - // .then(undefined, (e) => { - // getLogger().error('Failed to open security issue panel: %s', e.message) - // }) - // } - // await updateSecurityIssueWebview({ - // isGenerateFixLoading: true, - // // eslint-disable-next-line unicorn/no-null - // generateFixError: null, - // context: context.extensionContext, - // filePath: targetFilePath, - // shouldRefreshView: false, - // }) - - // codeFixState.setToRunning() - // let hasSuggestedFix = false - // const { suggestedFix, jobId } = await startCodeFixGeneration( - // client, - // targetIssue, - // targetFilePath, - // targetIssue.findingId - // ) - // // redact the fix if the user disabled references and there is a reference - // if ( - // // TODO: enable references later for scans - // // !CodeWhispererSettings.instance.isSuggestionsWithCodeReferencesEnabled() && - // suggestedFix?.references && - // suggestedFix?.references?.length > 0 - // ) { - // getLogger().debug( - // `Received fix with reference and user settings disallow references. Job ID: ${jobId}` - // ) - // // TODO: re-enable notifications once references published - // // void vscode.window.showInformationMessage( - // // 'Your settings do not allow code generation with references.' - // // ) - // hasSuggestedFix = false - // } else { - // hasSuggestedFix = suggestedFix !== undefined - // } - // telemetry.record({ includesFix: hasSuggestedFix }) - // const updatedIssue: CodeScanIssue = { - // ...targetIssue, - // fixJobId: jobId, - // suggestedFixes: - // hasSuggestedFix && suggestedFix - // ? [ - // { - // code: suggestedFix.codeDiff, - // description: suggestedFix.description ?? '', - // references: suggestedFix.references, - // }, - // ] - // : [], - // } - // await updateSecurityIssueWebview({ - // issue: updatedIssue, - // isGenerateFixLoading: false, - // filePath: targetFilePath, - // context: context.extensionContext, - // shouldRefreshView: true, - // }) - - // SecurityIssueProvider.instance.updateIssue(updatedIssue, targetFilePath) - // SecurityIssueTreeViewProvider.instance.refresh() - // } catch (err) { - // const error = err instanceof Error ? err : new TypeError('Unexpected error') - // await updateSecurityIssueWebview({ - // issue: targetIssue, - // isGenerateFixLoading: false, - // generateFixError: getErrorMsg(error, true), - // filePath: targetFilePath, - // context: context.extensionContext, - // shouldRefreshView: false, - // }) - // SecurityIssueProvider.instance.updateIssue(targetIssue, targetFilePath) - // SecurityIssueTreeViewProvider.instance.refresh() - // throw err - // } finally { - // telemetry.record({ - // component: targetSource, - // detectorId: targetIssue.detectorId, - // findingId: targetIssue.findingId, - // ruleId: targetIssue.ruleId, - // variant: refresh ? 'refresh' : undefined, - // autoDetected: targetIssue.autoDetected, - // codewhispererCodeScanJobId: targetIssue.scanJobId, - // }) - // } - // }) } ) From ccc19de7e9d8998092ba92e03b1d4927bc50465e Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Sun, 6 Jul 2025 13:11:48 -0700 Subject: [PATCH 14/29] fix: update explain issue prompt --- packages/amazonq/src/lsp/chat/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index ffb0a634220..193770661e8 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -44,7 +44,7 @@ export function registerCommands(provider: AmazonQChatViewProvider) { const visibleMessageInChat = `_Explain **${issue.title}** issue in **${path.basename(filePath)}** at \`(${issue.startLine}, ${issue.endLine})\`_` // The message that gets sent to the backend - const contextMessage = `Provide a small description of the issue. Do not attempt to fix the issue, only explain it. Code issue - ${JSON.stringify(issue)}` + const contextMessage = `Provide a small description of the issue. Do not attempt to fix the issue, only give a small summary of it. Code issue - ${JSON.stringify(issue)}` void provider.webview?.postMessage({ command: 'sendToPrompt', From 11bcfc9b80a1d0a57588ad6a7f2d811bcf33ea12 Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Tue, 8 Jul 2025 10:56:05 -0700 Subject: [PATCH 15/29] fix(amazonq): change 'Generate Fix' to 'Fix' and remove form code issues --- packages/core/package.nls.json | 2 +- .../codewhisperer/service/securityIssueTreeViewProvider.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/core/package.nls.json b/packages/core/package.nls.json index b3cf958c980..c8c091a609e 100644 --- a/packages/core/package.nls.json +++ b/packages/core/package.nls.json @@ -146,7 +146,7 @@ "AWS.command.amazonq.generateUnitTests": "Generate Tests", "AWS.command.amazonq.security.scan": "Run Project Review", "AWS.command.amazonq.security.fileScan": "Run File Review", - "AWS.command.amazonq.generateFix": "Generate Fix", + "AWS.command.amazonq.generateFix": "Fix", "AWS.command.amazonq.viewDetails": "View Details", "AWS.command.amazonq.explainIssue": "Explain", "AWS.command.amazonq.ignoreIssue": "Ignore Issue", diff --git a/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts b/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts index d7c93f70423..b1f7f73907b 100644 --- a/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts @@ -189,11 +189,8 @@ export class IssueItem extends vscode.TreeItem { } private getDescription() { - const positionStr = `[Ln ${this.issue.startLine + 1}, Col 1]` const groupingStrategy = CodeIssueGroupingStrategyState.instance.getState() - return groupingStrategy !== CodeIssueGroupingStrategy.FileLocation - ? `${path.basename(this.filePath)} ${positionStr}` - : positionStr + return groupingStrategy !== CodeIssueGroupingStrategy.FileLocation ? `${path.basename(this.filePath)}` : '' } private getContextValue() { From f1b693633dd97e5bd276cb0f20943aa8260329b2 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Tue, 8 Jul 2025 15:28:38 -0700 Subject: [PATCH 16/29] fix: remove unnecessary imports --- packages/amazonq/src/lsp/chat/commands.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index 9ad61e2d9c7..0e0cc996f0f 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -7,8 +7,6 @@ import { Commands, globals } from 'aws-core-vscode/shared' import { window } from 'vscode' import { AmazonQChatViewProvider } from './webviewProvider' import { CodeScanIssue } from 'aws-core-vscode/codewhisperer' -import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' -import { EditorContextExtractor } from 'aws-core-vscode/codewhispererChat' import * as vscode from 'vscode' import * as path from 'path' From a479a42c4f14c5f129cde11b2a744cb04806d105 Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Tue, 8 Jul 2025 16:25:43 -0700 Subject: [PATCH 17/29] fix(amazonq): improve line numbering in Explain and Fix commands --- packages/amazonq/src/lsp/chat/commands.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index ffb0a634220..03739de4492 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -41,7 +41,11 @@ export function registerCommands(provider: AmazonQChatViewProvider) { }) } - const visibleMessageInChat = `_Explain **${issue.title}** issue in **${path.basename(filePath)}** at \`(${issue.startLine}, ${issue.endLine})\`_` + const lineRange = + issue.startLine === issue.endLine - 1 + ? `[${issue.startLine + 1}]` + : `[${issue.startLine + 1}, ${issue.endLine}]` + const visibleMessageInChat = `_Explain **${issue.title}** issue in **${path.basename(filePath)}** at \`${lineRange}\`_` // The message that gets sent to the backend const contextMessage = `Provide a small description of the issue. Do not attempt to fix the issue, only explain it. Code issue - ${JSON.stringify(issue)}` @@ -73,7 +77,11 @@ export function registerCommands(provider: AmazonQChatViewProvider) { }) } - const visibleMessageInChat = `_Fix **${issue.title}** issue in **${path.basename(filePath)}** at \`(${issue.startLine}, ${issue.endLine})\`_` + const lineRange = + issue.startLine === issue.endLine - 1 + ? `[${issue.startLine + 1}]` + : `[${issue.startLine + 1}, ${issue.endLine}]` + const visibleMessageInChat = `_Fix **${issue.title}** issue in **${path.basename(filePath)}** at \`${lineRange}\`_` // The message that gets sent to the backend const contextMessage = `Generate a fix for the following code issue. Do not explain the issue, just generate and explain the fix. The user should have the option to accept or reject the fix before any code is changed. Code issue - ${JSON.stringify(issue)}` From d512facec62747175d83744039f3abeb8ae42c6f Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Tue, 8 Jul 2025 17:16:03 -0700 Subject: [PATCH 18/29] fix: update explain and fix prompt --- packages/amazonq/src/lsp/chat/commands.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index 0ef12ba38bd..2876bf83b33 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -42,7 +42,7 @@ export function registerCommands(provider: AmazonQChatViewProvider) { const visibleMessageInChat = `_Explain **${issue.title}** issue in **${path.basename(filePath)}** at \`${lineRange}\`_` // The message that gets sent to the backend - const contextMessage = `Provide a small description of the issue. Do not attempt to fix the issue, only give a small summary of it. Code issue - ${JSON.stringify(issue)}` + const contextMessage = `Provide a small description of the issue. You must not attempt to fix the issue. You should only give a small summary of it to the user. Code issue - ${JSON.stringify(issue)}` void provider.webview?.postMessage({ command: 'sendToPrompt', @@ -78,7 +78,7 @@ export function registerCommands(provider: AmazonQChatViewProvider) { const visibleMessageInChat = `_Fix **${issue.title}** issue in **${path.basename(filePath)}** at \`${lineRange}\`_` // The message that gets sent to the backend - const contextMessage = `Generate a fix for the following code issue. Do not explain the issue, just generate and explain the fix. The user should have the option to accept or reject the fix before any code is changed. Code issue - ${JSON.stringify(issue)}` + const contextMessage = `Generate a fix for the following code issue. You must not explain the issue, just generate and explain the fix. The user should have the option to accept or reject the fix before any code is changed. Code issue - ${JSON.stringify(issue)}` void provider.webview?.postMessage({ command: 'sendToPrompt', From 170704067471354db7ca28a7d227771d0fa693cb Mon Sep 17 00:00:00 2001 From: Blake Lazarine Date: Wed, 9 Jul 2025 13:49:29 -0700 Subject: [PATCH 19/29] feat(amazonq): add agentic reviewer feature flag --- packages/amazonq/src/lsp/client.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 67f21ab396a..5f38c72a16f 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -165,6 +165,7 @@ export async function startLanguageServer( pinnedContextEnabled: true, mcp: true, workspaceFilePath: vscode.workspace.workspaceFile?.fsPath, + agenticReviewer: true, }, window: { notifications: true, From e50149e0fa7d098f33abdbbb8f250f45e220dfa3 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Fri, 11 Jul 2025 13:43:35 -0700 Subject: [PATCH 20/29] fix: update qCodeReview tool enable flag to --- packages/amazonq/src/lsp/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/src/lsp/client.ts b/packages/amazonq/src/lsp/client.ts index 3fb3f70c523..d36558d99f9 100644 --- a/packages/amazonq/src/lsp/client.ts +++ b/packages/amazonq/src/lsp/client.ts @@ -167,7 +167,7 @@ export async function startLanguageServer( reroute: true, modelSelection: true, workspaceFilePath: vscode.workspace.workspaceFile?.fsPath, - agenticReviewer: true, + qCodeReviewInChat: true, }, window: { notifications: true, From 81617eb9aedfe76807ab24f623e11effe0d3ec12 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Mon, 14 Jul 2025 15:09:17 -0700 Subject: [PATCH 21/29] fix: fix unit tests --- .../securityIssueHoverProvider.test.ts | 103 +++--------------- .../securityIssueTreeViewProvider.test.ts | 2 +- .../service/securityIssueHoverProvider.ts | 2 +- .../commands/basicCommands.test.ts | 48 -------- 4 files changed, 20 insertions(+), 135 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts index 956c3b43d73..63541f18dde 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts @@ -46,62 +46,29 @@ describe('securityIssueHoverProvider', () => { (actual.contents[0] as vscode.MarkdownString).value, '## title ![High](severity-high.svg)\n' + 'fix\n\n' + - `[$(eye) View Details](command:aws.amazonq.openSecurityIssuePanel?${encodeURIComponent( + `[$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Open "Code Issue Details"')\n` + - ` | [$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( - JSON.stringify([issues[0]]) )} 'Explain with Amazon Q')\n` + + ` | [$(comment) Fix](command:aws.amazonq.generateFix?${encodeURIComponent( + JSON.stringify([issues[0], mockDocument.fileName]) + )} 'Fix with Amazon Q')\n` + ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName, 'hover']) )} 'Ignore Issue')\n` + ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( JSON.stringify([issues[0], 'hover']) - )} 'Ignore Similar Issues')\n` + - ` | [$(wrench) Fix](command:aws.amazonq.applySecurityFix?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName, 'hover']) - )} 'Fix with Amazon Q')\n` + - '### Suggested Fix Preview\n\n' + - '\n\n' + - '```undefined\n' + - '@@ -1,1 +1,1 @@ \n' + - '```\n\n' + - '\n' + - '
\n' + - '\n\n' + - '```language\n' + - 'first line \n' + - '```\n\n' + - '\n' + - '
\n' + - '\n\n' + - '```diff\n' + - '-second line \n' + - '```\n\n' + - '\n' + - '
\n' + - '\n\n' + - '```diff\n' + - '+third line \n' + - '```\n\n' + - '\n' + - '
\n' + - '\n\n' + - '```language\n' + - 'fourth line \n' + - '```\n\n' + - '\n\n' + )} 'Ignore Similar Issues')\n` ) assert.strictEqual( (actual.contents[1] as vscode.MarkdownString).value, '## title ![High](severity-high.svg)\n' + 'recommendationText\n\n' + - `[$(eye) View Details](command:aws.amazonq.openSecurityIssuePanel?${encodeURIComponent( + `[$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( JSON.stringify([issues[1], mockDocument.fileName]) - )} 'Open "Code Issue Details"')\n` + - ` | [$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( - JSON.stringify([issues[1]]) )} 'Explain with Amazon Q')\n` + + ` | [$(comment) Fix](command:aws.amazonq.generateFix?${encodeURIComponent( + JSON.stringify([issues[1], mockDocument.fileName]) + )} 'Fix with Amazon Q')\n` + ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( JSON.stringify([issues[1], mockDocument.fileName, 'hover']) )} 'Ignore Issue')\n` + @@ -156,12 +123,12 @@ describe('securityIssueHoverProvider', () => { (actual.contents[0] as vscode.MarkdownString).value, '## title \n' + 'recommendationText\n\n' + - `[$(eye) View Details](command:aws.amazonq.openSecurityIssuePanel?${encodeURIComponent( + `[$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Open "Code Issue Details"')\n` + - ` | [$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( - JSON.stringify([issues[0]]) )} 'Explain with Amazon Q')\n` + + ` | [$(comment) Fix](command:aws.amazonq.generateFix?${encodeURIComponent( + JSON.stringify([issues[0], mockDocument.fileName]) + )} 'Fix with Amazon Q')\n` + ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName, 'hover']) )} 'Ignore Issue')\n` + @@ -194,52 +161,18 @@ describe('securityIssueHoverProvider', () => { (actual.contents[0] as vscode.MarkdownString).value, '## title ![High](severity-high.svg)\n' + 'fix\n\n' + - `[$(eye) View Details](command:aws.amazonq.openSecurityIssuePanel?${encodeURIComponent( + `[$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Open "Code Issue Details"')\n` + - ` | [$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( - JSON.stringify([issues[0]]) )} 'Explain with Amazon Q')\n` + + ` | [$(comment) Fix](command:aws.amazonq.generateFix?${encodeURIComponent( + JSON.stringify([issues[0], mockDocument.fileName]) + )} 'Fix with Amazon Q')\n` + ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( JSON.stringify([issues[0], mockDocument.fileName, 'hover']) )} 'Ignore Issue')\n` + ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( JSON.stringify([issues[0], 'hover']) - )} 'Ignore Similar Issues')\n` + - ` | [$(wrench) Fix](command:aws.amazonq.applySecurityFix?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName, 'hover']) - )} 'Fix with Amazon Q')\n` + - '### Suggested Fix Preview\n\n' + - '\n\n' + - '```undefined\n' + - '@@ -1,1 +1,1 @@ \n' + - '```\n\n' + - '\n' + - '
\n' + - '\n\n' + - '```language\n' + - 'first line \n' + - '```\n\n' + - '\n' + - '
\n' + - '\n\n' + - '```diff\n' + - '-second line \n' + - '-third line \n' + - '```\n\n' + - '\n' + - '
\n' + - '\n\n' + - '```diff\n' + - '+fourth line \n' + - '```\n\n' + - '\n' + - '
\n' + - '\n\n' + - '```language\n' + - 'fifth line \n' + - '```\n\n' + - '\n\n' + )} 'Ignore Similar Issues')\n` ) }) diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts index d72e1f8636f..6a74be85118 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts @@ -150,7 +150,7 @@ describe('SecurityIssueTreeViewProvider', function () { item.iconPath?.toString().includes(`${item.issue.severity.toLowerCase()}.svg`) ) ) - assert.ok(issueItems.every((item) => item.description?.toString().startsWith('[Ln '))) + assert.ok(issueItems.every((item) => !item.description?.toString().startsWith('[Ln '))) } }) }) diff --git a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts index 95ea3869cb0..c907f99abe3 100644 --- a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts @@ -92,7 +92,7 @@ export class SecurityIssueHoverProvider implements vscode.HoverProvider { [issue, filePath], 'comment', 'Fix', - 'Generate Fix for Issue' + 'Fix with Amazon Q' ) markdownString.appendMarkdown(' | ' + generateFixCommand) diff --git a/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts b/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts index b911c9687ee..354f6e63496 100644 --- a/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts +++ b/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts @@ -22,7 +22,6 @@ import { generateFix, rejectFix, ignoreIssue, - regenerateFix, ignoreAllIssues, } from '../../../codewhisperer/commands/basicCommands' import { FakeExtensionContext } from '../../fakeExtensionContext' @@ -1150,51 +1149,4 @@ def execute_input_compliant(): }) }) }) - - describe('regenerateFix', function () { - let sandbox: sinon.SinonSandbox - let filePath: string - let codeScanIssue: CodeScanIssue - let issueItem: IssueItem - let rejectFixMock: sinon.SinonStub - let generateFixMock: sinon.SinonStub - - beforeEach(function () { - sandbox = sinon.createSandbox() - filePath = 'dummy/file.py' - codeScanIssue = createCodeScanIssue({ - findingId: randomUUID(), - suggestedFixes: [{ code: 'diff', description: 'description' }], - }) - issueItem = new IssueItem(filePath, codeScanIssue) - rejectFixMock = sinon.stub() - generateFixMock = sinon.stub() - }) - - afterEach(function () { - sandbox.restore() - }) - - it('should call regenerateFix command successfully', async function () { - const updatedIssue = createCodeScanIssue({ findingId: 'updatedIssue' }) - sinon.stub(rejectFix, 'execute').value(rejectFixMock.resolves(updatedIssue)) - sinon.stub(generateFix, 'execute').value(generateFixMock) - targetCommand = testCommand(regenerateFix) - await targetCommand.execute(codeScanIssue, filePath) - - assert.ok(rejectFixMock.calledWith(codeScanIssue, filePath)) - assert.ok(generateFixMock.calledWith(updatedIssue, filePath)) - }) - - it('should call regenerateFix from tree view item', async function () { - const updatedIssue = createCodeScanIssue({ findingId: 'updatedIssue' }) - sinon.stub(rejectFix, 'execute').value(rejectFixMock.resolves(updatedIssue)) - sinon.stub(generateFix, 'execute').value(generateFixMock) - targetCommand = testCommand(regenerateFix) - await targetCommand.execute(issueItem, filePath) - - assert.ok(rejectFixMock.calledWith(codeScanIssue, filePath)) - assert.ok(generateFixMock.calledWith(updatedIssue, filePath)) - }) - }) }) From 8e8c9e98474ebb9bbfc6baaa4fac88e039f03f1e Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Mon, 14 Jul 2025 15:28:19 -0700 Subject: [PATCH 22/29] fix: remove tests for generateFix, as we are not generating fixes anymore, it is generated by maestro --- .../commands/basicCommands.test.ts | 168 ------------------ 1 file changed, 168 deletions(-) diff --git a/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts b/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts index 354f6e63496..2e505663a34 100644 --- a/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts +++ b/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts @@ -789,174 +789,6 @@ def execute_input_compliant(): }) }) - describe('generateFix', function () { - let sandbox: sinon.SinonSandbox - let mockClient: Stub - let startCodeFixGenerationStub: sinon.SinonStub - let filePath: string - let codeScanIssue: CodeScanIssue - let issueItem: IssueItem - let updateSecurityIssueWebviewMock: sinon.SinonStub - let updateIssueMock: sinon.SinonStub - let refreshTreeViewMock: sinon.SinonStub - let mockExtContext: ExtContext - - beforeEach(async function () { - sandbox = sinon.createSandbox() - mockClient = stub(DefaultCodeWhispererClient) - startCodeFixGenerationStub = sinon.stub(startCodeFixGeneration, 'startCodeFixGeneration') - filePath = 'dummy/file.py' - codeScanIssue = createCodeScanIssue({ - findingId: randomUUID(), - ruleId: 'dummy-rule-id', - }) - issueItem = new IssueItem(filePath, codeScanIssue) - updateSecurityIssueWebviewMock = sinon.stub(securityIssueWebview, 'updateSecurityIssueWebview') - updateIssueMock = sinon.stub(SecurityIssueProvider.instance, 'updateIssue') - refreshTreeViewMock = sinon.stub(SecurityIssueTreeViewProvider.instance, 'refresh') - mockExtContext = await FakeExtensionContext.getFakeExtContext() - }) - - afterEach(function () { - sandbox.restore() - }) - - it('should call generateFix command successfully', async function () { - startCodeFixGenerationStub.resolves({ - suggestedFix: { - codeDiff: 'codeDiff', - description: 'description', - references: [], - }, - jobId: 'jobId', - }) - - targetCommand = testCommand(generateFix, mockClient, mockExtContext) - await targetCommand.execute(codeScanIssue, filePath, 'webview') - - assert.ok(updateSecurityIssueWebviewMock.calledWith(sinon.match({ isGenerateFixLoading: true }))) - assert.ok( - startCodeFixGenerationStub.calledWith(mockClient, codeScanIssue, filePath, codeScanIssue.findingId) - ) - - const expectedUpdatedIssue = { - ...codeScanIssue, - fixJobId: 'jobId', - suggestedFixes: [{ code: 'codeDiff', description: 'description', references: [] }], - } - assert.ok( - updateSecurityIssueWebviewMock.calledWith( - sinon.match({ - issue: expectedUpdatedIssue, - isGenerateFixLoading: false, - filePath: filePath, - shouldRefreshView: true, - }) - ) - ) - assert.ok(updateIssueMock.calledWith(expectedUpdatedIssue, filePath)) - assert.ok(refreshTreeViewMock.calledOnce) - - assertTelemetry('codewhisperer_codeScanIssueGenerateFix', { - detectorId: codeScanIssue.detectorId, - findingId: codeScanIssue.findingId, - ruleId: codeScanIssue.ruleId, - component: 'webview', - result: 'Succeeded', - }) - }) - - it('should call generateFix from tree view item', async function () { - startCodeFixGenerationStub.resolves({ - suggestedFix: { - codeDiff: 'codeDiff', - description: 'description', - references: [], - }, - jobId: 'jobId', - }) - - targetCommand = testCommand(generateFix, mockClient, mockExtContext) - await targetCommand.execute(issueItem, filePath, 'tree') - - assertTelemetry('codewhisperer_codeScanIssueGenerateFix', { - detectorId: codeScanIssue.detectorId, - findingId: codeScanIssue.findingId, - ruleId: codeScanIssue.ruleId, - component: 'tree', - result: 'Succeeded', - }) - }) - - it('should call generateFix with refresh=true to indicate fix regenerated', async function () { - startCodeFixGenerationStub.resolves({ - suggestedFix: { - codeDiff: 'codeDiff', - description: 'description', - references: [], - }, - jobId: 'jobId', - }) - - targetCommand = testCommand(generateFix, mockClient, mockExtContext) - await targetCommand.execute(codeScanIssue, filePath, 'webview', true) - - assertTelemetry('codewhisperer_codeScanIssueGenerateFix', { - detectorId: codeScanIssue.detectorId, - findingId: codeScanIssue.findingId, - ruleId: codeScanIssue.ruleId, - component: 'webview', - result: 'Succeeded', - variant: 'refresh', - }) - }) - - it('should handle generateFix error', async function () { - startCodeFixGenerationStub.throws(new Error('Unexpected error')) - - targetCommand = testCommand(generateFix, mockClient, mockExtContext) - await targetCommand.execute(codeScanIssue, filePath, 'webview') - - assert.ok(updateSecurityIssueWebviewMock.calledWith(sinon.match({ isGenerateFixLoading: true }))) - assert.ok( - updateSecurityIssueWebviewMock.calledWith( - sinon.match({ - issue: codeScanIssue, - isGenerateFixLoading: false, - generateFixError: 'Unexpected error', - shouldRefreshView: false, - }) - ) - ) - assert.ok(updateIssueMock.calledWith(codeScanIssue, filePath)) - assert.ok(refreshTreeViewMock.calledOnce) - - assertTelemetry('codewhisperer_codeScanIssueGenerateFix', { - detectorId: codeScanIssue.detectorId, - findingId: codeScanIssue.findingId, - ruleId: codeScanIssue.ruleId, - component: 'webview', - result: 'Failed', - reason: 'Error', - reasonDesc: 'Unexpected error', - }) - }) - - it('exits early for SAS findings', async function () { - targetCommand = testCommand(generateFix, mockClient, mockExtContext) - codeScanIssue = createCodeScanIssue({ - ruleId: CodeWhispererConstants.sasRuleId, - }) - issueItem = new IssueItem(filePath, codeScanIssue) - await targetCommand.execute(codeScanIssue, filePath, 'webview') - assert.ok(updateSecurityIssueWebviewMock.notCalled) - assert.ok(startCodeFixGenerationStub.notCalled) - assert.ok(updateIssueMock.notCalled) - assert.ok(refreshTreeViewMock.notCalled) - assertNoTelemetryMatch('codewhisperer_codeScanIssueGenerateFix') - }) - }) - describe('rejectFix', function () { let mockExtensionContext: vscode.ExtensionContext let sandbox: sinon.SinonSandbox From a65e8e760b57ac57dd444aa6450925581e35d356 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Mon, 14 Jul 2025 15:38:35 -0700 Subject: [PATCH 23/29] fix: removed unused imports --- .../src/test/codewhisperer/commands/basicCommands.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts b/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts index 2e505663a34..460b45ab040 100644 --- a/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts +++ b/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts @@ -8,7 +8,7 @@ import assert from 'assert' import * as sinon from 'sinon' import * as CodeWhispererConstants from '../../../codewhisperer/models/constants' import { createCodeScanIssue, createMockDocument, resetCodeWhispererGlobalVariables } from '../testUtil' -import { assertNoTelemetryMatch, assertTelemetry, assertTelemetryCurried, tryRegister } from '../../testUtil' +import { assertTelemetry, assertTelemetryCurried, tryRegister } from '../../testUtil' import { toggleCodeSuggestions, showSecurityScan, @@ -19,7 +19,6 @@ import { reconnect, signoutCodeWhisperer, toggleCodeScans, - generateFix, rejectFix, ignoreIssue, ignoreAllIssues, @@ -29,7 +28,7 @@ import { testCommand } from '../../shared/vscode/testUtils' import { Command, placeholder } from '../../../shared/vscode/commands2' import { SecurityPanelViewProvider } from '../../../codewhisperer/views/securityPanelViewProvider' import { DefaultCodeWhispererClient } from '../../../codewhisperer/client/codewhisperer' -import { Stub, stub } from '../../utilities/stubber' +import { stub } from '../../utilities/stubber' import { AuthUtil } from '../../../codewhisperer/util/authUtil' import { getTestWindow } from '../../shared/vscode/window' import { ExtContext } from '../../../shared/extensions' @@ -67,7 +66,6 @@ import { SecurityIssueProvider } from '../../../codewhisperer/service/securityIs import { CodeWhispererSettings } from '../../../codewhisperer/util/codewhispererSettings' import { confirm } from '../../../shared' import * as commentUtils from '../../../shared/utilities/commentUtils' -import * as startCodeFixGeneration from '../../../codewhisperer/commands/startCodeFixGeneration' import * as extUtils from '../../../shared/extensionUtilities' describe('CodeWhisperer-basicCommands', function () { From 63b99ae130fbdeba828ba24ae6c504d57a54ea12 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Mon, 14 Jul 2025 16:31:57 -0700 Subject: [PATCH 24/29] fix: fix duplicate code --- packages/amazonq/src/lsp/chat/commands.ts | 148 +++++++++--------- .../securityIssueHoverProvider.test.ts | 127 ++++----------- 2 files changed, 100 insertions(+), 175 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index 2876bf83b33..e5f4d9564e4 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -22,78 +22,24 @@ export function registerCommands(provider: AmazonQChatViewProvider) { registerGenericCommand('aws.amazonq.optimizeCode', 'Optimize', provider), registerGenericCommand('aws.amazonq.generateUnitTests', 'Generate Tests', provider), - Commands.register('aws.amazonq.explainIssue', async (issue: CodeScanIssue, filePath: string) => { - void focusAmazonQPanel().then(async () => { - if (issue && filePath) { - const range = new vscode.Range(issue.startLine, 0, issue.endLine, 0) - await vscode.workspace.openTextDocument(filePath).then((doc) => { - void vscode.window.showTextDocument(doc, { - selection: range, - viewColumn: vscode.ViewColumn.One, - preview: true, - }) - }) - } - - const lineRange = - issue.startLine === issue.endLine - 1 - ? `[${issue.startLine + 1}]` - : `[${issue.startLine + 1}, ${issue.endLine}]` - const visibleMessageInChat = `_Explain **${issue.title}** issue in **${path.basename(filePath)}** at \`${lineRange}\`_` - - // The message that gets sent to the backend - const contextMessage = `Provide a small description of the issue. You must not attempt to fix the issue. You should only give a small summary of it to the user. Code issue - ${JSON.stringify(issue)}` - - void provider.webview?.postMessage({ - command: 'sendToPrompt', - params: { - selection: '', - triggerType: 'contextMenu', - prompt: { - prompt: visibleMessageInChat, // what gets sent to the user - escapedPrompt: contextMessage, // what gets sent to the backend - }, - autoSubmit: true, - }, - }) - }) - }), - Commands.register('aws.amazonq.generateFix', async (issue: CodeScanIssue, filePath: string) => { - void focusAmazonQPanel().then(async () => { - if (issue && filePath) { - const range = new vscode.Range(issue.startLine, 0, issue.endLine, 0) - await vscode.workspace.openTextDocument(filePath).then((doc) => { - void vscode.window.showTextDocument(doc, { - selection: range, - viewColumn: vscode.ViewColumn.One, - preview: true, - }) - }) - } - - const lineRange = - issue.startLine === issue.endLine - 1 - ? `[${issue.startLine + 1}]` - : `[${issue.startLine + 1}, ${issue.endLine}]` - const visibleMessageInChat = `_Fix **${issue.title}** issue in **${path.basename(filePath)}** at \`${lineRange}\`_` - - // The message that gets sent to the backend - const contextMessage = `Generate a fix for the following code issue. You must not explain the issue, just generate and explain the fix. The user should have the option to accept or reject the fix before any code is changed. Code issue - ${JSON.stringify(issue)}` - - void provider.webview?.postMessage({ - command: 'sendToPrompt', - params: { - selection: '', - triggerType: 'contextMenu', - prompt: { - prompt: visibleMessageInChat, // what gets sent to the user - escapedPrompt: contextMessage, // what gets sent to the backend - }, - autoSubmit: true, - }, - }) - }) - }), + Commands.register('aws.amazonq.explainIssue', (issue: CodeScanIssue, filePath: string) => + handleIssueCommand( + issue, + filePath, + 'Explain', + 'Provide a small description of the issue. You must not attempt to fix the issue. You should only give a small summary of it to the user.', + provider + ) + ), + Commands.register('aws.amazonq.generateFix', (issue: CodeScanIssue, filePath: string) => + handleIssueCommand( + issue, + filePath, + 'Fix', + 'Generate a fix for the following code issue. You must not explain the issue, just generate and explain the fix. The user should have the option to accept or reject the fix before any code is changed.', + provider + ) + ), Commands.register('aws.amazonq.sendToPrompt', (data) => { const triggerType = getCommandTriggerType(data) const selection = getSelectedText() @@ -116,6 +62,53 @@ export function registerCommands(provider: AmazonQChatViewProvider) { ) } +async function handleIssueCommand( + issue: CodeScanIssue, + filePath: string, + action: string, + contextPrompt: string, + provider: AmazonQChatViewProvider +) { + await focusAmazonQPanel() + + if (issue && filePath) { + await openFileWithSelection(issue, filePath) + } + + const lineRange = createLineRangeText(issue) + const visibleMessageInChat = `_${action} **${issue.title}** issue in **${path.basename(filePath)}** at \`${lineRange}\`_` + const contextMessage = `${contextPrompt} Code issue - ${JSON.stringify(issue)}` + + void provider.webview?.postMessage({ + command: 'sendToPrompt', + params: { + selection: '', + triggerType: 'contextMenu', + prompt: { + prompt: visibleMessageInChat, + escapedPrompt: contextMessage, + }, + autoSubmit: true, + }, + }) +} + +async function openFileWithSelection(issue: CodeScanIssue, filePath: string) { + const range = new vscode.Range(issue.startLine, 0, issue.endLine, 0) + const doc = await vscode.workspace.openTextDocument(filePath) + await vscode.window.showTextDocument(doc, { + selection: range, + viewColumn: vscode.ViewColumn.One, + preview: true, + }) +} + +function createLineRangeText(issue: CodeScanIssue): string { + return issue.startLine === issue.endLine - 1 + ? `[${issue.startLine + 1}]` + : `[${issue.startLine + 1}, ${issue.endLine}]` +} + function getSelectedText(): string { const editor = window.activeTextEditor if (editor) { @@ -134,15 +127,14 @@ function getCommandTriggerType(data: any): string { } function registerGenericCommand(commandName: string, genericCommand: string, provider: AmazonQChatViewProvider) { - return Commands.register(commandName, (data) => { + return Commands.register(commandName, async (data) => { const triggerType = getCommandTriggerType(data) const selection = getSelectedText() - void focusAmazonQPanel().then(() => { - void provider.webview?.postMessage({ - command: 'genericCommand', - params: { genericCommand, selection, triggerType }, - }) + await focusAmazonQPanel() + void provider.webview?.postMessage({ + command: 'genericCommand', + params: { genericCommand, selection, triggerType }, }) }) } diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts index 63541f18dde..26b3e723d03 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts @@ -21,6 +21,25 @@ describe('securityIssueHoverProvider', () => { token = new vscode.CancellationTokenSource() }) + function buildCommandLink(command: string, args: any[], label: string, tooltip: string): string { + return `[$(${command.includes('comment') ? 'comment' : 'error'}) ${label}](command:${command}?${encodeURIComponent(JSON.stringify(args))} '${tooltip}')` + } + + function buildExpectedContent(issue: any, fileName: string, description: string, severity?: string): string { + const severityBadge = severity ? ` ![${severity}](severity-${severity.toLowerCase()}.svg)` : ' ' + const commands = [ + buildCommandLink('aws.amazonq.explainIssue', [issue, fileName], 'Explain', 'Explain with Amazon Q'), + buildCommandLink('aws.amazonq.generateFix', [issue, fileName], 'Fix', 'Fix with Amazon Q'), + buildCommandLink('aws.amazonq.security.ignore', [issue, fileName, 'hover'], 'Ignore', 'Ignore Issue'), + buildCommandLink('aws.amazonq.security.ignoreAll', [issue, 'hover'], 'Ignore All', 'Ignore Similar Issues'), + ] + return `## title${severityBadge}\n${description}\n\n${commands.join('\n | ')}\n` + } + + function setupIssues(issues: any[]): void { + securityIssueProvider.issues = [{ filePath: mockDocument.fileName, issues }] + } + it('should return hover for each issue for the current position', () => { const issues = [ createCodeScanIssue({ findingId: 'finding-1', detectorId: 'language/detector-1', ruleId: 'Rule-123' }), @@ -32,49 +51,17 @@ describe('securityIssueHoverProvider', () => { }), ] - securityIssueProvider.issues = [ - { - filePath: mockDocument.fileName, - issues, - }, - ] - + setupIssues(issues) const actual = securityIssueHoverProvider.provideHover(mockDocument, new vscode.Position(0, 0), token.token) assert.strictEqual(actual.contents.length, 2) assert.strictEqual( (actual.contents[0] as vscode.MarkdownString).value, - '## title ![High](severity-high.svg)\n' + - 'fix\n\n' + - `[$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Explain with Amazon Q')\n` + - ` | [$(comment) Fix](command:aws.amazonq.generateFix?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Fix with Amazon Q')\n` + - ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName, 'hover']) - )} 'Ignore Issue')\n` + - ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( - JSON.stringify([issues[0], 'hover']) - )} 'Ignore Similar Issues')\n` + buildExpectedContent(issues[0], mockDocument.fileName, 'fix', 'High') ) assert.strictEqual( (actual.contents[1] as vscode.MarkdownString).value, - '## title ![High](severity-high.svg)\n' + - 'recommendationText\n\n' + - `[$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( - JSON.stringify([issues[1], mockDocument.fileName]) - )} 'Explain with Amazon Q')\n` + - ` | [$(comment) Fix](command:aws.amazonq.generateFix?${encodeURIComponent( - JSON.stringify([issues[1], mockDocument.fileName]) - )} 'Fix with Amazon Q')\n` + - ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( - JSON.stringify([issues[1], mockDocument.fileName, 'hover']) - )} 'Ignore Issue')\n` + - ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( - JSON.stringify([issues[1], 'hover']) - )} 'Ignore Similar Issues')\n` + buildExpectedContent(issues[1], mockDocument.fileName, 'recommendationText', 'High') ) assertTelemetry('codewhisperer_codeScanIssueHover', [ { findingId: 'finding-1', detectorId: 'language/detector-1', ruleId: 'Rule-123', includesFix: true }, @@ -83,27 +70,15 @@ describe('securityIssueHoverProvider', () => { }) it('should return empty contents if there is no issue on the current position', () => { - securityIssueProvider.issues = [ - { - filePath: mockDocument.fileName, - issues: [createCodeScanIssue()], - }, - ] - + setupIssues([createCodeScanIssue()]) const actual = securityIssueHoverProvider.provideHover(mockDocument, new vscode.Position(2, 0), token.token) assert.strictEqual(actual.contents.length, 0) }) it('should skip issues not in the current file', () => { securityIssueProvider.issues = [ - { - filePath: 'some/path', - issues: [createCodeScanIssue()], - }, - { - filePath: mockDocument.fileName, - issues: [createCodeScanIssue()], - }, + { filePath: 'some/path', issues: [createCodeScanIssue()] }, + { filePath: mockDocument.fileName, issues: [createCodeScanIssue()] }, ] const actual = securityIssueHoverProvider.provideHover(mockDocument, new vscode.Position(0, 0), token.token) assert.strictEqual(actual.contents.length, 1) @@ -111,30 +86,12 @@ describe('securityIssueHoverProvider', () => { it('should not show severity badge if undefined', () => { const issues = [createCodeScanIssue({ severity: undefined, suggestedFixes: [] })] - securityIssueProvider.issues = [ - { - filePath: mockDocument.fileName, - issues, - }, - ] + setupIssues(issues) const actual = securityIssueHoverProvider.provideHover(mockDocument, new vscode.Position(0, 0), token.token) assert.strictEqual(actual.contents.length, 1) assert.strictEqual( (actual.contents[0] as vscode.MarkdownString).value, - '## title \n' + - 'recommendationText\n\n' + - `[$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Explain with Amazon Q')\n` + - ` | [$(comment) Fix](command:aws.amazonq.generateFix?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Fix with Amazon Q')\n` + - ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName, 'hover']) - )} 'Ignore Issue')\n` + - ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( - JSON.stringify([issues[0], 'hover']) - )} 'Ignore Similar Issues')\n` + buildExpectedContent(issues[0], mockDocument.fileName, 'recommendationText') ) }) @@ -149,41 +106,17 @@ describe('securityIssueHoverProvider', () => { ], }), ] - securityIssueProvider.issues = [ - { - filePath: mockDocument.fileName, - issues, - }, - ] + setupIssues(issues) const actual = securityIssueHoverProvider.provideHover(mockDocument, new vscode.Position(0, 0), token.token) assert.strictEqual(actual.contents.length, 1) assert.strictEqual( (actual.contents[0] as vscode.MarkdownString).value, - '## title ![High](severity-high.svg)\n' + - 'fix\n\n' + - `[$(comment) Explain](command:aws.amazonq.explainIssue?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Explain with Amazon Q')\n` + - ` | [$(comment) Fix](command:aws.amazonq.generateFix?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName]) - )} 'Fix with Amazon Q')\n` + - ` | [$(error) Ignore](command:aws.amazonq.security.ignore?${encodeURIComponent( - JSON.stringify([issues[0], mockDocument.fileName, 'hover']) - )} 'Ignore Issue')\n` + - ` | [$(error) Ignore All](command:aws.amazonq.security.ignoreAll?${encodeURIComponent( - JSON.stringify([issues[0], 'hover']) - )} 'Ignore Similar Issues')\n` + buildExpectedContent(issues[0], mockDocument.fileName, 'fix', 'High') ) }) it('should not show issues that are not visible', () => { - const issues = [createCodeScanIssue({ visible: false })] - securityIssueProvider.issues = [ - { - filePath: mockDocument.fileName, - issues, - }, - ] + setupIssues([createCodeScanIssue({ visible: false })]) const actual = securityIssueHoverProvider.provideHover(mockDocument, new vscode.Position(0, 0), token.token) assert.strictEqual(actual.contents.length, 0) }) From b7950e6bfa3a9daf00648d8b2cd7e81011c29f3a Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Mon, 14 Jul 2025 17:17:49 -0700 Subject: [PATCH 25/29] fix: fix unit tests --- .../codewhisperer/service/securityIssueHoverProvider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts index 26b3e723d03..9c1bb751a35 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts @@ -22,7 +22,7 @@ describe('securityIssueHoverProvider', () => { }) function buildCommandLink(command: string, args: any[], label: string, tooltip: string): string { - return `[$(${command.includes('comment') ? 'comment' : 'error'}) ${label}](command:${command}?${encodeURIComponent(JSON.stringify(args))} '${tooltip}')` + return `[$(${command.includes('ignore') ? 'error' : 'comment'}) ${label}](command:${command}?${encodeURIComponent(JSON.stringify(args))} '${tooltip}')` } function buildExpectedContent(issue: any, fileName: string, description: string, severity?: string): string { From 92a2d01b95d4174b9ef133e267dce09925c84ad3 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Mon, 14 Jul 2025 17:45:14 -0700 Subject: [PATCH 26/29] fix: update change log for CodeIssues related changes --- .../Feature-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/amazonq/.changes/next-release/Feature-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json diff --git a/packages/amazonq/.changes/next-release/Feature-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json b/packages/amazonq/.changes/next-release/Feature-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json new file mode 100644 index 00000000000..5f92fc1d2f7 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Feature-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "Two main update about CodeIssues panel 1) Explain and Fix for any issue in CodeIssues panel will pull the experience into chat. 2) QCodeReview tool will update CodeIssues panel along with `/review` quick actions." +} From c9164a11495205da4d4a39c65a243cb42f340a00 Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Mon, 14 Jul 2025 22:01:26 -0700 Subject: [PATCH 27/29] fix: addressed pr comments --- ...-9e413673-5ef6-4920-97b1-e73635f3a0f5.json | 4 +++ ...-a0140eaf-abe8-43ae-9ea1-f0b1afcbc962.json | 4 +++ ...-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json | 4 --- packages/amazonq/src/lsp/chat/commands.ts | 11 ++++---- packages/amazonq/src/lsp/chat/messages.ts | 1 + packages/core/src/codewhisperer/activation.ts | 1 - .../codewhisperer/commands/basicCommands.ts | 12 +-------- .../service/diagnosticsProvider.ts | 1 - .../service/securityIssueProvider.ts | 27 +------------------ .../commands/basicCommands.test.ts | 2 ++ 10 files changed, 19 insertions(+), 48 deletions(-) create mode 100644 packages/amazonq/.changes/next-release/Feature-9e413673-5ef6-4920-97b1-e73635f3a0f5.json create mode 100644 packages/amazonq/.changes/next-release/Feature-a0140eaf-abe8-43ae-9ea1-f0b1afcbc962.json delete mode 100644 packages/amazonq/.changes/next-release/Feature-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json diff --git a/packages/amazonq/.changes/next-release/Feature-9e413673-5ef6-4920-97b1-e73635f3a0f5.json b/packages/amazonq/.changes/next-release/Feature-9e413673-5ef6-4920-97b1-e73635f3a0f5.json new file mode 100644 index 00000000000..172163ca908 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Feature-9e413673-5ef6-4920-97b1-e73635f3a0f5.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "Explain and Fix for any issue in Code Issues panel will pull the experience into chat" +} diff --git a/packages/amazonq/.changes/next-release/Feature-a0140eaf-abe8-43ae-9ea1-f0b1afcbc962.json b/packages/amazonq/.changes/next-release/Feature-a0140eaf-abe8-43ae-9ea1-f0b1afcbc962.json new file mode 100644 index 00000000000..1048a3e14c0 --- /dev/null +++ b/packages/amazonq/.changes/next-release/Feature-a0140eaf-abe8-43ae-9ea1-f0b1afcbc962.json @@ -0,0 +1,4 @@ +{ + "type": "Feature", + "description": "QCodeReview tool will update CodeIssues panel along with quick action - `/review`" +} diff --git a/packages/amazonq/.changes/next-release/Feature-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json b/packages/amazonq/.changes/next-release/Feature-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json deleted file mode 100644 index 5f92fc1d2f7..00000000000 --- a/packages/amazonq/.changes/next-release/Feature-a11ff37a-40bc-43e3-a779-004f52d5d5f7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "Feature", - "description": "Two main update about CodeIssues panel 1) Explain and Fix for any issue in CodeIssues panel will pull the experience into chat. 2) QCodeReview tool will update CodeIssues panel along with `/review` quick actions." -} diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index 03fdb72ebaa..1c70a887dad 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -130,14 +130,15 @@ function getCommandTriggerType(data: any): string { } function registerGenericCommand(commandName: string, genericCommand: string, provider: AmazonQChatViewProvider) { - return Commands.register(commandName, async (data) => { + return Commands.register(commandName, (data) => { const triggerType = getCommandTriggerType(data) const selection = getSelectedText() - await focusAmazonQPanel() - void provider.webview?.postMessage({ - command: 'genericCommand', - params: { genericCommand, selection, triggerType }, + void focusAmazonQPanel().then(() => { + void provider.webview?.postMessage({ + command: 'genericCommand', + params: { genericCommand, selection, triggerType }, + }) }) }) } diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 72661f95eda..8496f996123 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -625,6 +625,7 @@ async function handlePartialResult( ) { const decryptedMessage = await decryptResponse(partialResult, encryptionKey) + // This is to filter out the message containing findings from qCodeReview tool to update CodeIssues panel decryptedMessage.additionalMessages = decryptedMessage.additionalMessages?.filter( (message) => !(message.messageId !== undefined && message.messageId.endsWith(CodeWhispererConstants.findingsSuffix)) diff --git a/packages/core/src/codewhisperer/activation.ts b/packages/core/src/codewhisperer/activation.ts index 11bbce5a817..d6dd7fdc61d 100644 --- a/packages/core/src/codewhisperer/activation.ts +++ b/packages/core/src/codewhisperer/activation.ts @@ -476,7 +476,6 @@ export async function activate(context: ExtContext): Promise { CodeWhispererConstants.amazonqIgnoreNextLine ) ) - // SecurityIssueProvider.instance.cleanOldFiles() }), vscode.window.createTreeView(SecurityIssueTreeViewProvider.viewType, { treeDataProvider: SecurityIssueTreeViewProvider.instance, diff --git a/packages/core/src/codewhisperer/commands/basicCommands.ts b/packages/core/src/codewhisperer/commands/basicCommands.ts index c7df25bf824..745fe1a45a9 100644 --- a/packages/core/src/codewhisperer/commands/basicCommands.ts +++ b/packages/core/src/codewhisperer/commands/basicCommands.ts @@ -366,10 +366,6 @@ export const openSecurityIssuePanel = Commands.declare( const targetIssue: CodeScanIssue = issue instanceof IssueItem ? issue.issue : issue const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath await showSecurityIssueWebview(context.extensionContext, targetIssue, targetFilePath) - - // if (targetIssue.suggestedFixes.length === 0) { - // await generateFix.execute(targetIssue, targetFilePath, 'webview', true, false) - // } telemetry.codewhisperer_codeScanIssueViewDetails.emit({ findingId: targetIssue.findingId, detectorId: targetIssue.detectorId, @@ -719,13 +715,7 @@ export const rejectFix = Commands.declare( export const regenerateFix = Commands.declare( { id: 'aws.amazonq.security.regenerateFix' }, - () => async (issue: CodeScanIssue | IssueItem | undefined, filePath: string, source: Component) => { - // const targetIssue: CodeScanIssue | undefined = issue instanceof IssueItem ? issue.issue : issue - // const targetFilePath: string = issue instanceof IssueItem ? issue.filePath : filePath - // const targetSource: Component = issue instanceof IssueItem ? 'tree' : source - // const updatedIssue = await rejectFix.execute(targetIssue, targetFilePath) - // await generateFix.execute(updatedIssue, targetFilePath, targetSource, true) - } + () => async (issue: CodeScanIssue | IssueItem | undefined, filePath: string, source: Component) => {} ) export const explainIssue = Commands.declare( diff --git a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts index e110f37c121..f181bdb146d 100644 --- a/packages/core/src/codewhisperer/service/diagnosticsProvider.ts +++ b/packages/core/src/codewhisperer/service/diagnosticsProvider.ts @@ -39,7 +39,6 @@ export function initSecurityScanRender( updateSecurityIssuesForProviders(securityRecommendation, scope === CodeAnalysisScope.FILE_AUTO) } securityScanRender.initialized = true - // SecurityIssueProvider.instance.cleanOldFiles() } function updateSecurityIssuesForProviders(securityRecommendation: AggregatedCodeScanIssue, isAutoScope?: boolean) { diff --git a/packages/core/src/codewhisperer/service/securityIssueProvider.ts b/packages/core/src/codewhisperer/service/securityIssueProvider.ts index 4727aa43caa..d055cb0a7d5 100644 --- a/packages/core/src/codewhisperer/service/securityIssueProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueProvider.ts @@ -6,8 +6,7 @@ import * as vscode from 'vscode' import { AggregatedCodeScanIssue, CodeScanIssue, SuggestedFix } from '../models/model' import { randomUUID } from '../../shared/crypto' -import fs = require('fs') -import path from 'path' + export class SecurityIssueProvider { static #instance: SecurityIssueProvider public static get instance() { @@ -169,28 +168,4 @@ export class SecurityIssueProvider { (i) => i.title === issue.title && i.startLine === issue.startLine && i.endLine === issue.endLine ) } - - public cleanOldFiles(maxfiles = 100): void { - const issuesJson = JSON.stringify(this._issues) - const dirPath = path.join(__dirname, '..', '..', '..', '..', '..', '..', 'findings') - fs.existsSync(dirPath) || fs.mkdirSync(dirPath) - fs.writeFileSync(path.join(dirPath, `SecurityIssues-${this._id}.json`), issuesJson, 'utf8') - const files = fs.readdirSync(dirPath) - if (files.length > maxfiles) { - type Stat = { fileName: string; mtime: number } - const stats: Stat[] = [] - for (const file of files) { - const stat = fs.statSync(path.join(dirPath, file[0])) - stats.push({ - fileName: file[0], - mtime: stat.mtime.getTime(), - }) - } - const sortedStats = stats.sort((a: Stat, b: Stat) => a.mtime - b.mtime) - const numberToDelete = files.length - maxfiles - for (let i = 0; i < numberToDelete; i++) { - fs.rmSync(path.join(dirPath, sortedStats[i].fileName)) - } - } - } } diff --git a/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts b/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts index 460b45ab040..05164274b70 100644 --- a/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts +++ b/packages/core/src/test/codewhisperer/commands/basicCommands.test.ts @@ -787,6 +787,8 @@ def execute_input_compliant(): }) }) + // TODO: Add integ test for generateTest + describe('rejectFix', function () { let mockExtensionContext: vscode.ExtensionContext let sandbox: sinon.SinonSandbox From f6183dc4c8571fa8f688852138742c7b4068c27f Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Mon, 14 Jul 2025 22:56:17 -0700 Subject: [PATCH 28/29] fix: show and log error when error while open file with selection --- packages/amazonq/src/lsp/chat/commands.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/amazonq/src/lsp/chat/commands.ts b/packages/amazonq/src/lsp/chat/commands.ts index 1c70a887dad..83e70b7bae3 100644 --- a/packages/amazonq/src/lsp/chat/commands.ts +++ b/packages/amazonq/src/lsp/chat/commands.ts @@ -7,6 +7,7 @@ import { Commands, globals } from 'aws-core-vscode/shared' import { window } from 'vscode' import { AmazonQChatViewProvider } from './webviewProvider' import { CodeScanIssue } from 'aws-core-vscode/codewhisperer' +import { getLogger } from 'aws-core-vscode/shared' import * as vscode from 'vscode' import * as path from 'path' @@ -97,13 +98,18 @@ async function handleIssueCommand( } async function openFileWithSelection(issue: CodeScanIssue, filePath: string) { - const range = new vscode.Range(issue.startLine, 0, issue.endLine, 0) - const doc = await vscode.workspace.openTextDocument(filePath) - await vscode.window.showTextDocument(doc, { - selection: range, - viewColumn: vscode.ViewColumn.One, - preview: true, - }) + try { + const range = new vscode.Range(issue.startLine, 0, issue.endLine, 0) + const doc = await vscode.workspace.openTextDocument(filePath) + await vscode.window.showTextDocument(doc, { + selection: range, + viewColumn: vscode.ViewColumn.One, + preview: true, + }) + } catch (e) { + getLogger().error('openFileWithSelection: Failed to open file %s with selection: %O', filePath, e) + void vscode.window.showInformationMessage('Failed to display file with issue.') + } } function createLineRangeText(issue: CodeScanIssue): string { From 534f875d2ff71a0647fdde7e67113dc55163b56e Mon Sep 17 00:00:00 2001 From: Nitish Kumar Singh Date: Mon, 14 Jul 2025 23:20:40 -0700 Subject: [PATCH 29/29] fix: update change log --- .../Feature-9e413673-5ef6-4920-97b1-e73635f3a0f5.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amazonq/.changes/next-release/Feature-9e413673-5ef6-4920-97b1-e73635f3a0f5.json b/packages/amazonq/.changes/next-release/Feature-9e413673-5ef6-4920-97b1-e73635f3a0f5.json index 172163ca908..af699a24355 100644 --- a/packages/amazonq/.changes/next-release/Feature-9e413673-5ef6-4920-97b1-e73635f3a0f5.json +++ b/packages/amazonq/.changes/next-release/Feature-9e413673-5ef6-4920-97b1-e73635f3a0f5.json @@ -1,4 +1,4 @@ { "type": "Feature", - "description": "Explain and Fix for any issue in Code Issues panel will pull the experience into chat" + "description": "Explain and Fix for any issue in Code Issues panel will pull the experience into chat. Also no more view details tab." }