Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f76d69d
feat(amazonq): add support for CodeReview and DisplayFindings tools, …
blakelaz-amazon Aug 13, 2025
b05c4f1
fix(amazonq): remove commented lines
blakelaz-amazon Aug 14, 2025
99d8433
feat(amazonq): add support for CodeReview and DisplayFindings tools, …
blakelaz-amazon Aug 13, 2025
ec2d4fd
fix(amazonq): remove commented lines
blakelaz-amazon Aug 14, 2025
19f51a9
fix(amazonq): general code quality fixes for CodeReview and DisplayFi…
blakelaz-amazon Aug 14, 2025
d28f486
Merge remote-tracking branch 'origin/codeReview' into codeReview
blakelaz-amazon Aug 14, 2025
ea41226
fix(amazonq): Prevents Q UriError : Validate file URI and fallback to…
Lee-WonJun Aug 14, 2025
d37106e
feat(amazonq): show the opt-in checkbox for server-side context (#5894)
LiGaCu Aug 14, 2025
fe668ee
fix(amazonq): delete AwsServerCapabilitiesProvider (#5833)
rli Aug 14, 2025
f21c028
feat(amazonq): add support for CodeReview and DisplayFindings tools, …
blakelaz-amazon Aug 13, 2025
683189f
Merge branch 'main' into codeReview
blakelaz-amazon Aug 15, 2025
45b7fe2
Revert "feat(amazonq): add support for CodeReview and DisplayFindings…
blakelaz-amazon Aug 15, 2025
2588777
fix(amazonq): fix formatting issues
blakelaz-amazon Aug 15, 2025
7d4404d
fix(amazonq): fix more formatting issues
blakelaz-amazon Aug 15, 2025
5e878f9
fix(amazonq): fix build failures
blakelaz-amazon Aug 15, 2025
b26778c
fix(amazonq): undo unrelated change
blakelaz-amazon Aug 15, 2025
5d2c6f5
fix(amazonq): address codeReview comments
blakelaz-amazon Aug 16, 2025
8ed39dd
fix(amazonq): fixing detekt errors
blakelaz-amazon Aug 16, 2025
b0ebef0
feat(amazonq): emit telemetry events for explainIssue and applyFix co…
blakelaz-amazon Aug 16, 2025
3725505
Merge branch 'main' into codeReview
BlakeLazarine Aug 18, 2025
e913055
feat(amazonq): add unit tests for parseFindingsMessage
blakelaz-amazon Aug 18, 2025
f8e45e8
fix(amazonq): fix detekt errors
blakelaz-amazon Aug 18, 2025
9819471
Merge branch 'main' into codeReview
BlakeLazarine Aug 18, 2025
685db1a
fix(amazonq): fix re-direct of /review action
blakelaz-amazon Aug 19, 2025
7baa5f2
Merge branch 'main' into codeReview
laileni-aws Aug 20, 2025
683944b
fix(amazonq): improve handling of case where messageId is null
blakelaz-amazon Aug 22, 2025
98e8b3c
Merge branch 'main' into codeReview
BlakeLazarine Aug 22, 2025
9debeac
fix(amazonq): Add changelog, code quality improvements
blakelaz-amazon Aug 22, 2025
2a007ad
Merge branch 'main' into codeReview
BlakeLazarine Aug 22, 2025
718153b
fix(amazonq): address comments
blakelaz-amazon Aug 22, 2025
f675828
fix(amazonq): remove unused import
blakelaz-amazon Aug 22, 2025
f9c0976
Merge branch 'main' into codeReview
BlakeLazarine Aug 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type" : "feature",
"description" : "Enable agentic code review"
}
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ mockitoKotlin = "5.4.1-SNAPSHOT"
mockk = "1.13.17"
nimbus-jose-jwt = "9.40"
node-gradle = "7.0.2"
telemetryGenerator = "1.0.322"
telemetryGenerator = "1.0.329"
testLogger = "4.0.0"
testRetry = "1.5.10"
# test-only; platform provides slf4j transitively at runtime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
import com.intellij.ide.BrowserUtil
import com.intellij.ide.util.RunOnceUtil
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.jcef.JBCefJSQuery.Response
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
Expand Down Expand Up @@ -71,6 +75,8 @@
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_BAR_ACTIONS
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_CHANGE
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CHAT_TAB_REMOVE
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.CODE_REVIEW_FINDINGS_SUFFIX
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.DISPLAY_FINDINGS_SUFFIX
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedChatParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.EncryptedQuickActionChatParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.GET_SERIALIZED_CHAT_REQUEST_METHOD
Expand Down Expand Up @@ -107,13 +113,21 @@
import software.aws.toolkits.jetbrains.services.amazonqDoc.auth.isDocAvailable
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeatureDevAvailable
import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanIssue
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.CodeWhispererCodeScanManager
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Description
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.Recommendation
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.SuggestedFix
import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
import software.aws.toolkits.jetbrains.settings.MeetQSettings
import software.aws.toolkits.telemetry.MetricResult
import software.aws.toolkits.telemetry.Telemetry
import java.nio.file.Path
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
import java.util.function.Function
import kotlin.collections.get

Check warning on line 130 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt

View workflow job for this annotation

GitHub Actions / qodana

Unused import directive

Unused import directive

class BrowserConnector(
private val serializer: MessageSerializer = MessageSerializer.getInstance(),
Expand Down Expand Up @@ -568,6 +582,32 @@
}
}

data class FlareCodeScanIssue(
val startLine: Int,
val endLine: Int,
val comment: String?,
val title: String,
val description: Description,
val detectorId: String,
val detectorName: String,
val findingId: String,
val ruleId: String?,
val relatedVulnerabilities: List<String>,
val severity: String,
val recommendation: Recommendation,
val suggestedFixes: List<SuggestedFix>,
val scanJobId: String,
val language: String,
val autoDetected: Boolean,
val filePath: String,
val findingContext: String,
)

data class AggregatedCodeScanIssue(
val filePath: String,
val issues: List<FlareCodeScanIssue>,
)

private fun showResult(
result: CompletableFuture<String>,
partialResultToken: String,
Expand All @@ -581,10 +621,14 @@
throw error
}
chatCommunicationManager.removePartialChatMessage(partialResultToken)
val decryptedMessage = Gson().fromJson(value?.let { encryptionManager?.decrypt(it) }.orEmpty(), Map::class.java)
as Map<String, *>
parseFindingsMessages(decryptedMessage)

val messageToChat = ChatCommunicationManager.convertToJsonToSendToChat(
SEND_CHAT_COMMAND_PROMPT,
tabId,
value?.let { encryptionManager?.decrypt(it) }.orEmpty(),
Gson().toJson(decryptedMessage),
isPartialResult = false
)
browser.postChat(messageToChat)
Expand All @@ -598,6 +642,85 @@
}
}

fun parseFindingsMessages(messagesMap: Map<String, *>) {
try {
val additionalMessages = messagesMap["additionalMessages"] as? MutableList<Map<String, Any>>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this any?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typing is very complex and only the shape of additionalMessages matters. Even then I am not confident in my ability to know the shape of every possible entry here. Only the shape of the findings message matters. Introducing forced shapes would be too risky in my opinion

val findingsMessages = additionalMessages?.filter { message ->
if (message.contains("messageId")) {
(message["messageId"] as String).endsWith(CODE_REVIEW_FINDINGS_SUFFIX) ||
(message["messageId"] as String).endsWith(DISPLAY_FINDINGS_SUFFIX)
} else {
false
}
}
val scannedFiles = mutableListOf<VirtualFile>()
if (findingsMessages != null) {
for (findingsMessage in findingsMessages) {
additionalMessages.remove(findingsMessage)
val gson = Gson()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would recommend using the same instance of Gson throughout the class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add in follow up PR

val jsonFindings = gson.fromJson(findingsMessage["body"] as String, List::class.java)
val mappedFindings = mutableListOf<CodeWhispererCodeScanIssue>()
for (aggregatedIssueUnformatted in jsonFindings) {
val aggregatedIssue = gson.fromJson(gson.toJson(aggregatedIssueUnformatted), AggregatedCodeScanIssue::class.java)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are receiving a List of AggregatedCodeScanIssues as a string from FLARE message, this is formatting them and putting them into a List object.

val file = LocalFileSystem.getInstance().findFileByIoFile(
Path.of(aggregatedIssue.filePath).toFile()
)
if (file?.isDirectory == false) {
scannedFiles.add(file)
runReadAction {
FileDocumentManager.getInstance().getDocument(file)
}?.let { document ->
for (issue in aggregatedIssue.issues) {
val endLineInDocument = minOf(maxOf(0, issue.endLine - 1), document.lineCount - 1)
val endCol = document.getLineEndOffset(endLineInDocument) - document.getLineStartOffset(endLineInDocument) + 1
val isIssueIgnored = CodeWhispererCodeScanManager.getInstance(project)
.isIgnoredIssue(issue.title, document, file, issue.startLine - 1)
if (isIssueIgnored) {
continue
}
mappedFindings.add(
CodeWhispererCodeScanIssue(
startLine = issue.startLine,
startCol = 1,
endLine = issue.endLine,
endCol = endCol,
file = file,
project = project,
title = issue.title,
description = issue.description,
detectorId = issue.detectorId,
detectorName = issue.detectorName,
findingId = issue.findingId,
ruleId = issue.ruleId,
relatedVulnerabilities = issue.relatedVulnerabilities,
severity = issue.severity,
recommendation = issue.recommendation,
suggestedFixes = issue.suggestedFixes,
codeSnippet = emptyList(),
isVisible = true,
autoDetected = issue.autoDetected,
scanJobId = issue.scanJobId,
),
)
}
}
}
}

CodeWhispererCodeScanManager.getInstance(project)
.addOnDemandIssues(
mappedFindings,
scannedFiles,
CodeWhispererConstants.CodeAnalysisScope.AGENTIC
)
CodeWhispererCodeScanManager.getInstance(project).showCodeScanUI()
}
}
} catch (e: Exception) {
LOG.error { "Failed to parse findings message $e" }
}
}

private suspend fun updateQuickActionsInBrowser(browser: Browser) {
val isFeatureDevAvailable = isFeatureDevAvailable(project)
val isCodeTransformAvailable = isCodeTransformAvailable(project)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ enum class EditorContextCommand(
verb = "sendToPrompt",
actionId = "aws.amazonq.sendToPrompt",
),
ExplainCodeScanIssue(
verb = "ExplainIssue",
actionId = "aws.amazonq.explainCodeScanIssue",
HandleCodeScanIssue(
verb = "HandleCodeScanIssue",
actionId = "aws.amazonq.handleCodeScanIssueCommand",
),
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,56 @@
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SEND_TO_PROMPT
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendToPromptParams
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TriggerType
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.TelemetryHelper
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl

class HandleIssueCommandAction : AnAction(), DumbAware {

Check warning on line 23 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/codescan/actions/HandleIssueCommandAction.kt

View workflow job for this annotation

GitHub Actions / qodana

Component/Action not registered

Action is not registered in plugin.xml

Check warning

Code scanning / QDJVMC

Component/Action not registered Warning

Action is not registered in plugin.xml
val contextDataKey = DataKey.create<MutableMap<String, String>>("amazonq.codescan.handleIssueCommandContext")
val actionDataKey = DataKey.create<String>("amazonq.codescan.handleIssueCommandAction")
Comment on lines +24 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val contextDataKey = DataKey.create<MutableMap<String, String>>("amazonq.codescan.handleIssueCommandContext")
val actionDataKey = DataKey.create<String>("amazonq.codescan.handleIssueCommandAction")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add in follow up PR


class ExplainCodeIssueAction : AnAction(), DumbAware {
override fun getActionUpdateThread() = ActionUpdateThread.BGT

override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = e.project != null
}
fun createLineRangeText(issueContext: MutableMap<String, String>): String {
val startLine = issueContext["startLine"]
val endLine = issueContext["endLine"]
return if (startLine.equals(endLine)) {
"[$startLine]"
} else {
"[$startLine, $endLine]"
}
}

override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val issueDataKey = DataKey.create<MutableMap<String, String>>("amazonq.codescan.explainissue")
val issueContext = e.getData(issueDataKey) ?: return
val context = e.getData(contextDataKey) ?: return
val action = e.getData(actionDataKey) ?: return

// Emit telemetry event
TelemetryHelper.recordTelemetryIssueCommandAction(
context["findingId"].orEmpty(),
context["detectorId"].orEmpty(),
context["ruleId"].orEmpty(),
context["autoDetected"].orEmpty(),
getStartUrl(project).orEmpty(),
action, // The action name (explainIssue or applyFix)
"Succeeded"
)

ActionManager.getInstance().getAction("q.openchat").actionPerformed(e)

ApplicationManager.getApplication().executeOnPooledThread {
runBlocking {
// https://github.com/aws/aws-toolkit-vscode/blob/master/packages/amazonq/src/lsp/chat/commands.ts#L30
val codeSelection = "\n```\n${issueContext["code"]?.trimIndent()?.trim()}\n```\n"
val codeSelection = "\n```\n${context["code"]?.trimIndent()?.trim()}\n```\n"
val actionString = if (action == "explainIssue") "Explain" else "Fix"

val prompt = "Explain the issue \n\n " +
"Issue: \"${issueContext["title"]}\" \n" +
"Code: $codeSelection"
val prompt = "$actionString ${context["title"]} issue in ${context["fileName"]} at ${createLineRangeText(context)}"

val modelPrompt = "Explain the issue ${issueContext["title"]} \n\n " +
"Issue: \"${issueContext["title"]}\" \n" +
"Description: ${issueContext["description"]} \n" +
"Code: $codeSelection and generate the code demonstrating the fix"
val modelPrompt = "$actionString ${context["title"]} issue in ${context["fileName"]} at ${createLineRangeText(context)}" +
"Issue: \"${context}\" \n"

val params = SendToPromptParams(
selection = codeSelection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,26 @@ class TelemetryHelper(private val project: Project, private val sessionStorage:
.credentialStartUrl(startUrl)
}
}

fun recordTelemetryIssueCommandAction(
findingId: String,
detectorId: String,
ruleId: String,
autoDetected: String,
startUrl: String,
metricName: String,
result: String,
) {
Telemetry.amazonq.codeReviewTool.use {
it.reason(metricName)
.setAttribute("findingId", findingId)
.setAttribute("detectorId", detectorId)
.setAttribute("ruleId", ruleId)
.setAttribute("credentialStartUrl", startUrl)
.setAttribute("autoDetected", autoDetected)
.setAttribute("result", result)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class UserIntentRecognizer {
EditorContextCommand.Refactor -> UserIntent.SUGGEST_ALTERNATE_IMPLEMENTATION
EditorContextCommand.Fix -> UserIntent.APPLY_COMMON_BEST_PRACTICES
EditorContextCommand.Optimize -> UserIntent.IMPROVE_CODE
EditorContextCommand.ExplainCodeScanIssue -> UserIntent.EXPLAIN_CODE_SELECTION
EditorContextCommand.HandleCodeScanIssue -> UserIntent.EXPLAIN_CODE_SELECTION
EditorContextCommand.GenerateUnitTests -> UserIntent.GENERATE_UNIT_TESTS
EditorContextCommand.SendToPrompt -> null
}
Expand Down
Loading
Loading