From c6673d0f8fe84469c811cc60194ccb86eaac0361 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:37:16 +0100 Subject: [PATCH 01/43] Add placeholder to prompt input --- .../stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt | 1 + .../src/main/resources/messages/SecureCoderBundle.properties | 1 + 2 files changed, 2 insertions(+) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt index 028534c..bacc861 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt @@ -118,6 +118,7 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { rows = 5 columns = 30 border = Borders.empty(8) + getEmptyText().setText(SecureCoderBundle.message("edit.placeholder")); } private fun wrapTextInScrollPane(inputArea: JBTextArea): JBScrollPane = JBScrollPane(inputArea).apply { diff --git a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties index b6cae37..b280cee 100644 --- a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties +++ b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties @@ -15,6 +15,7 @@ edit.proposed.changes.title=Proposed Changes - {0} edit.empty=Empty edit.current.content=Current content edit.with.changes=With changes +edit.placeholder=Type your task here... edit.apply=Apply edit.apply.tooltip=Apply all pending changes From f6b1e13be4680c3ff51a3a501aab48982f188ca4 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:51:07 +0100 Subject: [PATCH 02/43] Publish setting changes into own topic --- .../settings/SecureCoderSettingsConfigurable.kt | 8 ++++++++ .../plugin/settings/SecureCoderSettingsState.kt | 14 +++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsConfigurable.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsConfigurable.kt index 860d3c6..7bc59a8 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsConfigurable.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsConfigurable.kt @@ -162,4 +162,12 @@ class SecureCoderSettingsConfigurable : BoundConfigurable(SecureCoderBundle.mess }.enabledIf(codeql.selected) } } + + override fun apply() { + super.apply() + ApplicationManager.getApplication() + .messageBus + .syncPublisher(SecureCoderSettingsState.topic) + .settingsChanged(settings.state) + } } diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsState.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsState.kt index 5a5e883..9a08fc2 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsState.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsState.kt @@ -4,6 +4,7 @@ import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.Service import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage +import com.intellij.util.messages.Topic @State(name = "SecureCoderSettings", storages = [Storage("SecureCoderSettings.xml")]) @Service(Service.Level.APP) @@ -22,7 +23,7 @@ class SecureCoderSettingsState : PersistentStateComponent = Topic.create( + "SecureCoderSettingsChanged", + SecureCoderSettingsListener::class.java + ) + } + private var state: StateData = StateData() override fun getState(): StateData = state From 8f6a6a8b9a60ae4eb5089ff594ce63379d7a4d26 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:55:02 +0100 Subject: [PATCH 03/43] Show warning when llm provider is not configured --- .../plugin/SecureCoderAiToolWindowFactory.kt | 101 ++++++++++++------ .../settings/SecureCoderSettingsState.kt | 6 +- .../messages/SecureCoderBundle.properties | 4 + 3 files changed, 78 insertions(+), 33 deletions(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt index bacc861..ff591b3 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt @@ -4,6 +4,7 @@ import com.intellij.icons.AllIcons import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.EDT +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.service import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.project.DumbAware @@ -23,6 +24,8 @@ import de.tuda.stg.securecoder.plugin.engine.EngineRunnerService import de.tuda.stg.securecoder.plugin.engine.IntelliJProjectFileSystem import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent import de.tuda.stg.securecoder.plugin.settings.SecureCoderSettingsConfigurable +import de.tuda.stg.securecoder.plugin.settings.SecureCoderSettingsState +import de.tuda.stg.securecoder.plugin.settings.SecureCoderSettingsState.SecureCoderSettingsListener import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.awt.BorderLayout @@ -61,43 +64,79 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { } } )) + + val connection = ApplicationManager.getApplication().messageBus.connect(toolWindow.disposable) + connection.subscribe(SecureCoderSettingsState.topic, SecureCoderSettingsListener { + SwingUtilities.invokeLater { + content.component = createRoot(project) + } + }) } - private fun createRoot(project: Project): JPanel = JPanel(BorderLayout()).apply { - val inputArea = createInputArea() - val scroll = wrapTextInScrollPane(inputArea) - val preferredHeight = scroll.preferredSize.height - scroll.maximumSize = Dimension(Int.MAX_VALUE, preferredHeight) - val submit = createSubmitButton(preferredHeight) - val checkBox = JCheckBox(SecureCoderBundle.message("toolwindow.useWholeProject"), true).apply { - alignmentX = Component.LEFT_ALIGNMENT - border = Borders.emptyLeft(4) - toolTipText = SecureCoderBundle.message("toolwindow.useWholeProject.tooltip") - } - val header = JPanel().apply { - layout = BoxLayout(this, BoxLayout.Y_AXIS) - border = Borders.empty(8) - alignmentX = Component.LEFT_ALIGNMENT + private fun createRoot(project: Project): JPanel { + if (!service().state.hasLLMProviderConfigured()) { + return JPanel(BorderLayout()).apply { + border = Borders.empty(12) + val panel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + alignmentX = Component.LEFT_ALIGNMENT + } + val title = JLabel(SecureCoderBundle.message("toolwindow.notConfigured.title")).apply { + font = JBFont.h2() + border = Borders.emptyBottom(6) + } + val desc = JLabel(SecureCoderBundle.message("toolwindow.notConfigured.desc")) + val openSettings = JButton(SecureCoderBundle.message("toolwindow.notConfigured.openSettings")).apply { + alignmentX = Component.LEFT_ALIGNMENT + addActionListener { + ShowSettingsUtil.getInstance() + .showSettingsDialog(project, SecureCoderSettingsConfigurable::class.java) + } + } + panel.add(title) + panel.add(desc) + panel.add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) + panel.add(openSettings) + add(panel, BorderLayout.NORTH) + } } - val textRow = buildTextRow(scroll) - val controlsRow = buildControlsRow(checkBox, submit) - header.add(textRow) - header.add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) - header.add(controlsRow) - add(header, BorderLayout.NORTH) + return JPanel(BorderLayout()).apply { + val inputArea = createInputArea() + val scroll = wrapTextInScrollPane(inputArea) + val preferredHeight = scroll.preferredSize.height + scroll.maximumSize = Dimension(Int.MAX_VALUE, preferredHeight) + val submit = createSubmitButton(preferredHeight) + val checkBox = JCheckBox(SecureCoderBundle.message("toolwindow.useWholeProject"), true).apply { + alignmentX = Component.LEFT_ALIGNMENT + border = Borders.emptyLeft(4) + toolTipText = SecureCoderBundle.message("toolwindow.useWholeProject.tooltip") + } + val header = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + border = Borders.empty(8) + alignmentX = Component.LEFT_ALIGNMENT + } + val textRow = buildTextRow(scroll) + val controlsRow = buildControlsRow(checkBox, submit) - eventsPanel = JPanel().apply { - layout = BoxLayout(this, BoxLayout.Y_AXIS) - border = Borders.empty(8) - } - eventsScrollPane = JBScrollPane(eventsPanel).apply { - verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED - horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER - border = Borders.empty() + header.add(textRow) + header.add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) + header.add(controlsRow) + add(header, BorderLayout.NORTH) + + eventsPanel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + border = Borders.empty(8) + } + eventsScrollPane = JBScrollPane(eventsPanel).apply { + verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED + horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER + border = Borders.empty() + } + add(eventsScrollPane, BorderLayout.CENTER) + setupSubmitAction(project, inputArea, submit) } - add(eventsScrollPane, BorderLayout.CENTER) - setupSubmitAction(project, inputArea, submit) } private fun buildControlsRow(checkBox: JCheckBox, submit: JButton): JPanel = JPanel().apply { diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsState.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsState.kt index 9a08fc2..b6c4516 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsState.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/SecureCoderSettingsState.kt @@ -31,9 +31,11 @@ class SecureCoderSettingsState : PersistentStateComponent Date: Mon, 22 Dec 2025 17:01:14 +0100 Subject: [PATCH 04/43] Move SecureCoderAiToolWindowFactory into own package --- .../SecureCoderAiToolWindowFactory.kt | 55 ++++++++++--------- .../src/main/resources/META-INF/plugin.xml | 2 +- 2 files changed, 30 insertions(+), 27 deletions(-) rename app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/{ => toolwindow}/SecureCoderAiToolWindowFactory.kt (89%) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt similarity index 89% rename from app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt rename to app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt index ff591b3..3c7adbb 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/SecureCoderAiToolWindowFactory.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt @@ -1,10 +1,10 @@ -package de.tuda.stg.securecoder.plugin +package de.tuda.stg.securecoder.plugin.toolwindow import com.intellij.icons.AllIcons import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.application.EDT import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.EDT import com.intellij.openapi.components.service import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.project.DumbAware @@ -17,15 +17,13 @@ import com.intellij.ui.components.JBTextArea import com.intellij.ui.content.ContentFactory import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI -import com.intellij.util.ui.JBUI.Borders -import com.intellij.util.ui.JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground +import de.tuda.stg.securecoder.plugin.SecureCoderBundle import de.tuda.stg.securecoder.plugin.edit.buildEditFilesPanel import de.tuda.stg.securecoder.plugin.engine.EngineRunnerService import de.tuda.stg.securecoder.plugin.engine.IntelliJProjectFileSystem import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent import de.tuda.stg.securecoder.plugin.settings.SecureCoderSettingsConfigurable import de.tuda.stg.securecoder.plugin.settings.SecureCoderSettingsState -import de.tuda.stg.securecoder.plugin.settings.SecureCoderSettingsState.SecureCoderSettingsListener import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.awt.BorderLayout @@ -66,24 +64,26 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { )) val connection = ApplicationManager.getApplication().messageBus.connect(toolWindow.disposable) - connection.subscribe(SecureCoderSettingsState.topic, SecureCoderSettingsListener { - SwingUtilities.invokeLater { - content.component = createRoot(project) - } - }) + connection.subscribe( + SecureCoderSettingsState.topic, + SecureCoderSettingsState.SecureCoderSettingsListener { + SwingUtilities.invokeLater { + content.component = createRoot(project) + } + }) } private fun createRoot(project: Project): JPanel { if (!service().state.hasLLMProviderConfigured()) { return JPanel(BorderLayout()).apply { - border = Borders.empty(12) + border = JBUI.Borders.empty(12) val panel = JPanel().apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) alignmentX = Component.LEFT_ALIGNMENT } val title = JLabel(SecureCoderBundle.message("toolwindow.notConfigured.title")).apply { font = JBFont.h2() - border = Borders.emptyBottom(6) + border = JBUI.Borders.emptyBottom(6) } val desc = JLabel(SecureCoderBundle.message("toolwindow.notConfigured.desc")) val openSettings = JButton(SecureCoderBundle.message("toolwindow.notConfigured.openSettings")).apply { @@ -109,12 +109,12 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { val submit = createSubmitButton(preferredHeight) val checkBox = JCheckBox(SecureCoderBundle.message("toolwindow.useWholeProject"), true).apply { alignmentX = Component.LEFT_ALIGNMENT - border = Borders.emptyLeft(4) + border = JBUI.Borders.emptyLeft(4) toolTipText = SecureCoderBundle.message("toolwindow.useWholeProject.tooltip") } val header = JPanel().apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) - border = Borders.empty(8) + border = JBUI.Borders.empty(8) alignmentX = Component.LEFT_ALIGNMENT } val textRow = buildTextRow(scroll) @@ -127,12 +127,12 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { eventsPanel = JPanel().apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) - border = Borders.empty(8) + border = JBUI.Borders.empty(8) } eventsScrollPane = JBScrollPane(eventsPanel).apply { verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER - border = Borders.empty() + border = JBUI.Borders.empty() } add(eventsScrollPane, BorderLayout.CENTER) setupSubmitAction(project, inputArea, submit) @@ -141,7 +141,7 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { private fun buildControlsRow(checkBox: JCheckBox, submit: JButton): JPanel = JPanel().apply { layout = BoxLayout(this, BoxLayout.X_AXIS) - border = Borders.empty() + border = JBUI.Borders.empty() checkBox.alignmentY = Component.CENTER_ALIGNMENT checkBox.maximumSize = checkBox.preferredSize submit.alignmentY = Component.CENTER_ALIGNMENT @@ -156,7 +156,7 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { wrapStyleWord = true rows = 5 columns = 30 - border = Borders.empty(8) + border = JBUI.Borders.empty(8) getEmptyText().setText(SecureCoderBundle.message("edit.placeholder")); } @@ -171,7 +171,7 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { private fun buildTextRow(scroll: JBScrollPane): JPanel = JPanel().apply { layout = BoxLayout(this, BoxLayout.X_AXIS) - border = Borders.empty() + border = JBUI.Borders.empty() add(scroll.apply { alignmentX = Component.LEFT_ALIGNMENT maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height) @@ -217,14 +217,14 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { }.apply { layout = BoxLayout(this, BoxLayout.Y_AXIS) border = BorderFactory.createCompoundBorder( - Borders.customLine(separatorForeground(), 1), - Borders.empty(6) + JBUI.Borders.customLine(JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground(), 1), + JBUI.Borders.empty(6) ) } val titlePanel = JPanel().apply { layout = BoxLayout(this, BoxLayout.X_AXIS) - border = Borders.emptyBottom(4) + border = JBUI.Borders.emptyBottom(4) alignmentX = Component.LEFT_ALIGNMENT } val title = when (event) { @@ -245,11 +245,15 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { isEditable = false lineWrap = true wrapStyleWord = true - border = Borders.empty() + border = JBUI.Borders.empty() background = card.background alignmentX = Component.LEFT_ALIGNMENT } - is UiStreamEvent.EditFiles -> buildEditFilesPanel(project, event.changes, IntelliJProjectFileSystem(project)) + is UiStreamEvent.EditFiles -> buildEditFilesPanel( + project, + event.changes, + IntelliJProjectFileSystem(project) + ) } card.add(titlePanel) @@ -265,5 +269,4 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { vBar.value = vBar.maximum } } -} - +} \ No newline at end of file diff --git a/app/intellij-plugin/src/main/resources/META-INF/plugin.xml b/app/intellij-plugin/src/main/resources/META-INF/plugin.xml index 8fd1fa1..b9ed749 100644 --- a/app/intellij-plugin/src/main/resources/META-INF/plugin.xml +++ b/app/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -13,7 +13,7 @@ + factoryClass="de.tuda.stg.securecoder.plugin.toolwindow.SecureCoderAiToolWindowFactory"/> Date: Mon, 22 Dec 2025 17:11:31 +0100 Subject: [PATCH 05/43] Split up SecureCoderAiToolWindowFactory --- .../plugin/toolwindow/EventsPanel.kt | 68 ++++++ .../plugin/toolwindow/NotConfiguredPanel.kt | 43 ++++ .../plugin/toolwindow/PromptInputPanel.kt | 107 +++++++++ .../SecureCoderAiToolWindowFactory.kt | 213 +++--------------- 4 files changed, 248 insertions(+), 183 deletions(-) create mode 100644 app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt create mode 100644 app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/NotConfiguredPanel.kt create mode 100644 app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/PromptInputPanel.kt diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt new file mode 100644 index 0000000..101a645 --- /dev/null +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt @@ -0,0 +1,68 @@ +package de.tuda.stg.securecoder.plugin.toolwindow + +import com.intellij.icons.AllIcons +import com.intellij.openapi.project.Project +import com.intellij.util.ui.JBFont +import com.intellij.util.ui.JBUI +import de.tuda.stg.securecoder.plugin.edit.buildEditFilesPanel +import de.tuda.stg.securecoder.plugin.engine.IntelliJProjectFileSystem +import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent +import java.awt.Component +import java.awt.Dimension +import javax.swing.BorderFactory +import javax.swing.BoxLayout +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.JTextArea + +object EventsPanel { + fun addEventCard(event: UiStreamEvent, project: Project): JPanel { + val card = object : JPanel() { + override fun getMaximumSize() = Dimension(Int.MAX_VALUE, preferredSize.height) + }.apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + border = BorderFactory.createCompoundBorder( + JBUI.Borders.customLine(JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground(), 1), + JBUI.Borders.empty(6) + ) + } + + val titlePanel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + border = JBUI.Borders.emptyBottom(4) + alignmentX = Component.LEFT_ALIGNMENT + } + val title = when (event) { + is UiStreamEvent.Message -> event.title + is UiStreamEvent.EditFiles -> "Edit Files" + } + val icon = when (event) { + is UiStreamEvent.Message -> event.icon + is UiStreamEvent.EditFiles -> AllIcons.Actions.EditSource + } + val titleLabel = JLabel(title, icon, JLabel.LEADING).apply { + font = JBFont.label().asBold() + } + titlePanel.add(titleLabel) + + val content = when (event) { + is UiStreamEvent.Message -> JTextArea(event.description).apply { + isEditable = false + lineWrap = true + wrapStyleWord = true + border = JBUI.Borders.empty() + background = card.background + alignmentX = Component.LEFT_ALIGNMENT + } + is UiStreamEvent.EditFiles -> buildEditFilesPanel( + project, + event.changes, + IntelliJProjectFileSystem(project) + ) + } + + card.add(titlePanel) + card.add(content) + return card + } +} diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/NotConfiguredPanel.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/NotConfiguredPanel.kt new file mode 100644 index 0000000..f0a2a9d --- /dev/null +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/NotConfiguredPanel.kt @@ -0,0 +1,43 @@ +package de.tuda.stg.securecoder.plugin.toolwindow + +import com.intellij.openapi.options.ShowSettingsUtil +import com.intellij.openapi.project.Project +import com.intellij.util.ui.JBFont +import com.intellij.util.ui.JBUI +import de.tuda.stg.securecoder.plugin.SecureCoderBundle +import de.tuda.stg.securecoder.plugin.settings.SecureCoderSettingsConfigurable +import java.awt.BorderLayout +import java.awt.Component +import java.awt.Dimension +import javax.swing.Box +import javax.swing.BoxLayout +import javax.swing.JButton +import javax.swing.JLabel +import javax.swing.JPanel + +object NotConfiguredPanel { + fun build(project: Project): JPanel = JPanel(BorderLayout()).apply { + border = JBUI.Borders.empty(12) + val panel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + alignmentX = Component.LEFT_ALIGNMENT + } + val title = JLabel(SecureCoderBundle.message("toolwindow.notConfigured.title")).apply { + font = JBFont.h2() + border = JBUI.Borders.emptyBottom(6) + } + val desc = JLabel(SecureCoderBundle.message("toolwindow.notConfigured.desc")) + val openSettings = JButton(SecureCoderBundle.message("toolwindow.notConfigured.openSettings")).apply { + alignmentX = Component.LEFT_ALIGNMENT + addActionListener { + ShowSettingsUtil.getInstance() + .showSettingsDialog(project, SecureCoderSettingsConfigurable::class.java) + } + } + panel.add(title) + panel.add(desc) + panel.add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) + panel.add(openSettings) + add(panel, BorderLayout.NORTH) + } +} diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/PromptInputPanel.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/PromptInputPanel.kt new file mode 100644 index 0000000..4f13452 --- /dev/null +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/PromptInputPanel.kt @@ -0,0 +1,107 @@ +package de.tuda.stg.securecoder.plugin.toolwindow + +import com.intellij.openapi.project.Project +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.components.JBTextArea +import com.intellij.util.ui.JBUI +import de.tuda.stg.securecoder.plugin.SecureCoderBundle +import java.awt.Component +import java.awt.Dimension +import javax.swing.Box +import javax.swing.BoxLayout +import javax.swing.JButton +import javax.swing.JCheckBox +import javax.swing.JPanel +import javax.swing.ScrollPaneConstants +import com.intellij.openapi.ui.Messages + +class PromptInputPanel( + private val project: Project, + private val onSubmit: (promptText: String, onStreaming: () -> Unit, onFinished: () -> Unit) -> Unit +) { + val component: JPanel + + private val inputArea: JBTextArea = JBTextArea().apply { + lineWrap = true + wrapStyleWord = true + rows = 5 + columns = 30 + border = JBUI.Borders.empty(8) + getEmptyText().setText(SecureCoderBundle.message("edit.placeholder")) + } + + private fun wrapTextInScrollPane(): JBScrollPane = JBScrollPane(inputArea).apply { + verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED + horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER + } + + init { + val scroll = wrapTextInScrollPane() + val preferredHeight = scroll.preferredSize.height + scroll.maximumSize = Dimension(Int.MAX_VALUE, preferredHeight) + + val submit = JButton(SecureCoderBundle.message("toolwindow.submit")).apply { + maximumSize = Dimension(preferredSize.width, preferredHeight) + } + + val checkBox = JCheckBox(SecureCoderBundle.message("toolwindow.useWholeProject"), true).apply { + alignmentX = Component.LEFT_ALIGNMENT + border = JBUI.Borders.emptyLeft(4) + toolTipText = SecureCoderBundle.message("toolwindow.useWholeProject.tooltip") + } + + val textRow = JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + border = JBUI.Borders.empty() + add(scroll.apply { + alignmentX = Component.LEFT_ALIGNMENT + maximumSize = Dimension(Int.MAX_VALUE, preferredHeight) + }) + } + + val controlsRow = JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + border = JBUI.Borders.empty() + checkBox.alignmentY = Component.CENTER_ALIGNMENT + checkBox.maximumSize = checkBox.preferredSize + submit.alignmentY = Component.CENTER_ALIGNMENT + submit.maximumSize = Dimension(submit.preferredSize.width, submit.preferredSize.height) + add(checkBox) + add(Box.createHorizontalGlue()) + add(submit) + } + + component = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + border = JBUI.Borders.empty(8) + alignmentX = Component.LEFT_ALIGNMENT + add(textRow) + add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) + add(controlsRow) + } + + submit.addActionListener { + val text = inputArea.text.trim() + if (text.isEmpty()) { + Messages.showWarningDialog( + project, + SecureCoderBundle.message("warning.emptyPrompt"), + SecureCoderBundle.message("product.name") + ) + return@addActionListener + } + + val onStreaming = { + submit.isEnabled = false + submit.text = SecureCoderBundle.message("toolwindow.streaming") + } + + val onFinished = { + submit.isEnabled = true + submit.text = SecureCoderBundle.message("toolwindow.submit") + } + + onSubmit(text, onStreaming, onFinished) + } + } +} diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt index 3c7adbb..423e651 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt @@ -9,34 +9,23 @@ import com.intellij.openapi.components.service import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.Messages import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowFactory import com.intellij.ui.components.JBScrollPane -import com.intellij.ui.components.JBTextArea import com.intellij.ui.content.ContentFactory -import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI import de.tuda.stg.securecoder.plugin.SecureCoderBundle -import de.tuda.stg.securecoder.plugin.edit.buildEditFilesPanel import de.tuda.stg.securecoder.plugin.engine.EngineRunnerService -import de.tuda.stg.securecoder.plugin.engine.IntelliJProjectFileSystem import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent import de.tuda.stg.securecoder.plugin.settings.SecureCoderSettingsConfigurable import de.tuda.stg.securecoder.plugin.settings.SecureCoderSettingsState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.awt.BorderLayout -import java.awt.Component import java.awt.Dimension -import javax.swing.BorderFactory import javax.swing.Box import javax.swing.BoxLayout -import javax.swing.JButton -import javax.swing.JCheckBox -import javax.swing.JLabel import javax.swing.JPanel -import javax.swing.JTextArea import javax.swing.ScrollPaneConstants import javax.swing.SwingUtilities @@ -75,190 +64,48 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { private fun createRoot(project: Project): JPanel { if (!service().state.hasLLMProviderConfigured()) { - return JPanel(BorderLayout()).apply { - border = JBUI.Borders.empty(12) - val panel = JPanel().apply { - layout = BoxLayout(this, BoxLayout.Y_AXIS) - alignmentX = Component.LEFT_ALIGNMENT - } - val title = JLabel(SecureCoderBundle.message("toolwindow.notConfigured.title")).apply { - font = JBFont.h2() - border = JBUI.Borders.emptyBottom(6) - } - val desc = JLabel(SecureCoderBundle.message("toolwindow.notConfigured.desc")) - val openSettings = JButton(SecureCoderBundle.message("toolwindow.notConfigured.openSettings")).apply { - alignmentX = Component.LEFT_ALIGNMENT - addActionListener { - ShowSettingsUtil.getInstance() - .showSettingsDialog(project, SecureCoderSettingsConfigurable::class.java) - } - } - panel.add(title) - panel.add(desc) - panel.add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) - panel.add(openSettings) - add(panel, BorderLayout.NORTH) - } + return NotConfiguredPanel.build(project) } - return JPanel(BorderLayout()).apply { - val inputArea = createInputArea() - val scroll = wrapTextInScrollPane(inputArea) - val preferredHeight = scroll.preferredSize.height - scroll.maximumSize = Dimension(Int.MAX_VALUE, preferredHeight) - val submit = createSubmitButton(preferredHeight) - val checkBox = JCheckBox(SecureCoderBundle.message("toolwindow.useWholeProject"), true).apply { - alignmentX = Component.LEFT_ALIGNMENT - border = JBUI.Borders.emptyLeft(4) - toolTipText = SecureCoderBundle.message("toolwindow.useWholeProject.tooltip") - } - val header = JPanel().apply { - layout = BoxLayout(this, BoxLayout.Y_AXIS) - border = JBUI.Borders.empty(8) - alignmentX = Component.LEFT_ALIGNMENT - } - val textRow = buildTextRow(scroll) - val controlsRow = buildControlsRow(checkBox, submit) - - header.add(textRow) - header.add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) - header.add(controlsRow) - add(header, BorderLayout.NORTH) - - eventsPanel = JPanel().apply { - layout = BoxLayout(this, BoxLayout.Y_AXIS) - border = JBUI.Borders.empty(8) - } - eventsScrollPane = JBScrollPane(eventsPanel).apply { - verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED - horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER - border = JBUI.Borders.empty() - } - add(eventsScrollPane, BorderLayout.CENTER) - setupSubmitAction(project, inputArea, submit) - } - } - - private fun buildControlsRow(checkBox: JCheckBox, submit: JButton): JPanel = JPanel().apply { - layout = BoxLayout(this, BoxLayout.X_AXIS) - border = JBUI.Borders.empty() - checkBox.alignmentY = Component.CENTER_ALIGNMENT - checkBox.maximumSize = checkBox.preferredSize - submit.alignmentY = Component.CENTER_ALIGNMENT - submit.maximumSize = Dimension(submit.preferredSize.width, submit.preferredSize.height) - add(checkBox) - add(Box.createHorizontalGlue()) - add(submit) - } - - private fun createInputArea(): JBTextArea = JBTextArea().apply { - lineWrap = true - wrapStyleWord = true - rows = 5 - columns = 30 - border = JBUI.Borders.empty(8) - getEmptyText().setText(SecureCoderBundle.message("edit.placeholder")); - } - - private fun wrapTextInScrollPane(inputArea: JBTextArea): JBScrollPane = JBScrollPane(inputArea).apply { - verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED - horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER - } + val root = JPanel(BorderLayout()) - private fun createSubmitButton(height: Int): JButton = JButton(SecureCoderBundle.message("toolwindow.submit")).apply { - maximumSize = Dimension(preferredSize.width, height) - } + val promptPanel = PromptInputPanel(project) { promptText, onStreaming, onFinished -> + onStreaming() - private fun buildTextRow(scroll: JBScrollPane): JPanel = JPanel().apply { - layout = BoxLayout(this, BoxLayout.X_AXIS) - border = JBUI.Borders.empty() - add(scroll.apply { - alignmentX = Component.LEFT_ALIGNMENT - maximumSize = Dimension(Int.MAX_VALUE, preferredSize.height) - }) - } + val runner = project.service() + eventsPanel.removeAll() + eventsPanel.revalidate() + eventsPanel.repaint() - private fun setupSubmitAction(project: Project, inputArea: JBTextArea, submit: JButton) { - submit.addActionListener { - val text = inputArea.text.trim() - if (text.isEmpty()) { - Messages.showWarningDialog( - project, - SecureCoderBundle.message("warning.emptyPrompt"), - SecureCoderBundle.message("product.name") - ) - } else { - submit.isEnabled = false - submit.text = SecureCoderBundle.message("toolwindow.streaming") - - eventsPanel.removeAll() - eventsPanel.revalidate() - eventsPanel.repaint() - val runner = project.service() - runner.runEngine( - text, - onUiEvent = { event -> - withContext(Dispatchers.EDT) { addEventCard(event, project) } - }, - onComplete = { - withContext(Dispatchers.EDT) { - submit.isEnabled = true - submit.text = SecureCoderBundle.message("toolwindow.submit") - } - } - ) - } - } - } - - private fun addEventCard(event: UiStreamEvent, project: Project) { - val card = object : JPanel() { - override fun getMaximumSize() = Dimension(Int.MAX_VALUE, preferredSize.height) - }.apply { - layout = BoxLayout(this, BoxLayout.Y_AXIS) - border = BorderFactory.createCompoundBorder( - JBUI.Borders.customLine(JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground(), 1), - JBUI.Borders.empty(6) + runner.runEngine( + promptText, + onUiEvent = { event -> withContext(Dispatchers.EDT) { + addEventCard(event, project) + }}, + onComplete = { withContext(Dispatchers.EDT) { onFinished() } } ) } + root.add(promptPanel.component, BorderLayout.NORTH) - val titlePanel = JPanel().apply { - layout = BoxLayout(this, BoxLayout.X_AXIS) - border = JBUI.Borders.emptyBottom(4) - alignmentX = Component.LEFT_ALIGNMENT - } - val title = when (event) { - is UiStreamEvent.Message -> event.title - is UiStreamEvent.EditFiles -> "Edit Files" - } - val icon = when (event) { - is UiStreamEvent.Message -> event.icon - is UiStreamEvent.EditFiles -> AllIcons.Actions.EditSource - } - val titleLabel = JLabel(title, icon, JLabel.LEADING).apply { - font = JBFont.label().asBold() + eventsPanel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + border = JBUI.Borders.empty(8) } - titlePanel.add(titleLabel) - - val content = when (event) { - is UiStreamEvent.Message -> JTextArea(event.description).apply { - isEditable = false - lineWrap = true - wrapStyleWord = true - border = JBUI.Borders.empty() - background = card.background - alignmentX = Component.LEFT_ALIGNMENT - } - is UiStreamEvent.EditFiles -> buildEditFilesPanel( - project, - event.changes, - IntelliJProjectFileSystem(project) - ) + eventsScrollPane = JBScrollPane(eventsPanel).apply { + verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED + horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER + border = JBUI.Borders.empty() } + root.add(eventsScrollPane, BorderLayout.CENTER) - card.add(titlePanel) - card.add(content) + return root + } + private fun addEventCard( + event: UiStreamEvent, + project: Project + ) { + val card = EventsPanel.addEventCard(event, project) eventsPanel.add(card) eventsPanel.add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) eventsPanel.revalidate() From 7f0076324c904077fbf0910ae6d8c2d4ad74c972 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:21:45 +0100 Subject: [PATCH 06/43] Dont translate internal error messages --- .../plugin/settings/CodeQLInstaller.java | 14 +++++++------- .../messages/SecureCoderBundle.properties | 9 +-------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/CodeQLInstaller.java b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/CodeQLInstaller.java index e856a86..3ba5448 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/CodeQLInstaller.java +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/settings/CodeQLInstaller.java @@ -59,7 +59,7 @@ private String resolveCodeQLVersion(ProgressIndicator indicator) { try { return fetchLatestVersion(); } catch (IOException e) { - LOG.warn(SecureCoderBundle.INSTANCE.message("codeql.fetchLatestVersion.failed", FALLBACK_VERSION), e); + LOG.warn("Failed to fetch latest CodeQL version from GitHub API. Falling back to " + FALLBACK_VERSION, e); return FALLBACK_VERSION; } } @@ -76,13 +76,13 @@ private String fetchLatestVersion() throws IOException { LOG.info("Detected latest CodeQL version: " + version); return version; } - throw new IOException(SecureCoderBundle.INSTANCE.message("codeql.parseVersion.error")); + throw new IOException("Could not parse version from GitHub API response"); }); } private void installCodeQL(String version, Path installDir, ProgressIndicator indicator) throws IOException { String downloadUrl = getDownloadUrl(version); - LOG.info(SecureCoderBundle.INSTANCE.message("codeql.downloading.from", downloadUrl)); + LOG.info("Downloading CodeQL from: " + downloadUrl); File tempZip = FileUtil.createTempFile("codeql", ".zip"); @@ -102,7 +102,7 @@ private void installCodeQL(String version, Path installDir, ProgressIndicator in return parent != null && "bin".equals(parent.getFileName().toString()) && Files.isRegularFile(p); }).forEach(this::setExecutablePermissions); } catch (IOException e) { - LOG.warn(SecureCoderBundle.INSTANCE.message("codeql.walkToolsDir.failed", toolsDir.toString()), e); + LOG.warn("Failed to walk tools directory for permissions: " + toolsDir.toString(), e); } } setExecutablePermissions(executable); @@ -127,7 +127,7 @@ private String getDownloadUrl(String version) { } else if (SystemInfo.isLinux) { platform = "linux64"; } else { - throw new IllegalStateException(SecureCoderBundle.INSTANCE.message("codeql.unsupported.os", SystemInfo.OS_NAME)); + throw new IllegalStateException("Unsupported operating system: " + SystemInfo.OS_NAME); } return DOWNLOAD_BASE_URL + version + "/codeql-" + platform + ".zip"; } @@ -136,10 +136,10 @@ private void setExecutablePermissions(Path executable) { try { File file = executable.toFile(); if (!file.setExecutable(true, true)) { - throw new IOException(SecureCoderBundle.INSTANCE.message("codeql.setPermissions.failed", executable.toString())); + throw new IOException(""); } } catch (Exception e) { - LOG.warn(SecureCoderBundle.INSTANCE.message("codeql.setPermissions.failed", executable.toString()), e); + LOG.warn("Failed to set executable permissions for: " + executable, e); } } } diff --git a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties index d9cf925..4fed700 100644 --- a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties +++ b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties @@ -57,18 +57,11 @@ codeql.alreadyInstalled=CodeQL v{0} is already installed. codeql.downloading=Downloading CodeQL CLI v{0}... codeql.installation.missingExecutable=Installation finished, but executable not found at: {0} codeql.checkingVersion=Checking for latest CodeQL version... -codeql.fetchLatestVersion.failed=Failed to fetch latest CodeQL version from GitHub API. Falling back to {0} -codeql.parseVersion.error=Could not parse version from GitHub API response -codeql.downloading.from=Downloading CodeQL from: {0} -codeql.extracting=Extracting CodeQL... -codeql.walkToolsDir.failed=Failed to walk tools directory for permissions: {0} -codeql.unsupported.os=Unsupported operating system: {0} -codeql.setPermissions.failed=Failed to set executable permissions for: {0} +codeql.extracting=Extracting codeQL... settings.guardian.dummy=Dummy guardian settings.guardian.codeql.enable=Enable CodeQL guardian settings.codeql.binary=CodeQL binary -settings.codeql.selectBinary=Select CodeQL binary settings.codeql.test=Test settings.codeql.checking=Checking... settings.codeql.found=Found CodeQL {0} From dddc58d59ed5ba653f142be8f20a0b04bf66d766 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:27:39 +0100 Subject: [PATCH 07/43] Exclude coroutines libs from build --- app/intellij-plugin/build.gradle.kts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/intellij-plugin/build.gradle.kts b/app/intellij-plugin/build.gradle.kts index 7da5f6b..102bba3 100644 --- a/app/intellij-plugin/build.gradle.kts +++ b/app/intellij-plugin/build.gradle.kts @@ -22,6 +22,11 @@ kotlin { jvmToolchain(21) } +configurations.all { + exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core") + exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-jdk8") +} + tasks { patchPluginXml { changeNotes.set(""" From 33f99d52dbaca4626570f4fb4199db5e5e3dfa35 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:36:22 +0100 Subject: [PATCH 08/43] Fix project context control --- .../securecoder/plugin/toolwindow/PromptInputPanel.kt | 10 ++++------ .../toolwindow/SecureCoderAiToolWindowFactory.kt | 7 +++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/PromptInputPanel.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/PromptInputPanel.kt index 4f13452..2766f4b 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/PromptInputPanel.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/PromptInputPanel.kt @@ -17,7 +17,7 @@ import com.intellij.openapi.ui.Messages class PromptInputPanel( private val project: Project, - private val onSubmit: (promptText: String, onStreaming: () -> Unit, onFinished: () -> Unit) -> Unit + private val onSubmit: (promptText: String, projectAsContext: Boolean, onFinished: () -> Unit) -> Unit ) { val component: JPanel @@ -91,17 +91,15 @@ class PromptInputPanel( return@addActionListener } - val onStreaming = { - submit.isEnabled = false - submit.text = SecureCoderBundle.message("toolwindow.streaming") - } + submit.isEnabled = false + submit.text = SecureCoderBundle.message("toolwindow.streaming") val onFinished = { submit.isEnabled = true submit.text = SecureCoderBundle.message("toolwindow.submit") } - onSubmit(text, onStreaming, onFinished) + onSubmit(text, checkBox.isSelected, onFinished) } } } diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt index 423e651..dee75c8 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt @@ -69,9 +69,7 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { val root = JPanel(BorderLayout()) - val promptPanel = PromptInputPanel(project) { promptText, onStreaming, onFinished -> - onStreaming() - + val promptPanel = PromptInputPanel(project) { promptText, projectAsContext, onFinished -> val runner = project.service() eventsPanel.removeAll() eventsPanel.revalidate() @@ -82,7 +80,8 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { onUiEvent = { event -> withContext(Dispatchers.EDT) { addEventCard(event, project) }}, - onComplete = { withContext(Dispatchers.EDT) { onFinished() } } + onComplete = { withContext(Dispatchers.EDT) { onFinished() } }, + reduceContextToOpenFiles = !projectAsContext ) } root.add(promptPanel.component, BorderLayout.NORTH) From 1e24b6c44ed045dae04c62408a773190cf7b97bb Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:00:07 +0100 Subject: [PATCH 09/43] Write context as user role --- .../de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt index d8c15ae..b3f1c67 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt @@ -35,7 +35,7 @@ class WorkflowEngine ( val messages = mutableListOf( ChatMessage(Role.System, "You are a Security Engineering Agent mainly for writing secure code"), ChatMessage(Role.User, enrichedPrompt), - ChatMessage(Role.System, FilesInContextPromptBuilder.build(filesInContext, edit = true)), + ChatMessage(Role.User, FilesInContextPromptBuilder.build(filesInContext, edit = true)), ) repeat(maxGuardianRetries) { val out = editFiles.chat( @@ -43,7 +43,7 @@ class WorkflowEngine ( fileSystem = filesystem, onParseError = { onEvent(StreamEvent.InvalidLlmOutputWarning(it)) - }, + } ) messages += out.changesMessage() val changes = out.changes ?: return EngineResult.Failure.GenerationFailure From 4176fe32ac9cf1dc8bc8cfe91c76ca6eab203927 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Wed, 24 Dec 2025 22:16:10 +0100 Subject: [PATCH 10/43] Expose chat messages in InvalidLlmOutputWarning --- .../securecoder/engine/file/edit/EditFilesLlmWrapper.kt | 8 +++++--- .../de/tuda/stg/securecoder/engine/llm/ChatExchange.kt | 6 ++++++ .../de/tuda/stg/securecoder/engine/stream/StreamEvent.kt | 6 +++++- .../stg/securecoder/engine/workflow/WorkflowEngine.kt | 4 ++-- 4 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/ChatExchange.kt diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt index 48f3f9f..6a28b25 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt @@ -5,6 +5,7 @@ import de.tuda.stg.securecoder.engine.llm.ChatMessage import de.tuda.stg.securecoder.engine.llm.ChatMessage.Role import de.tuda.stg.securecoder.engine.llm.LlmClient import de.tuda.stg.securecoder.filesystem.FileSystem +import de.tuda.stg.securecoder.engine.llm.ChatExchange import kotlin.collections.plusAssign class EditFilesLlmWrapper( @@ -40,20 +41,21 @@ class EditFilesLlmWrapper( messages: List, fileSystem: FileSystem, params: LlmClient.GenerationParams = LlmClient.GenerationParams(), - onParseError: suspend (List) -> Unit = {}, + onParseError: suspend (parseErrors: List, llm: ChatExchange) -> Unit = { _, _ -> }, attempts: Int = 3 ): ChatResult { val messages = messages.toMutableList() messages += ChatMessage(Role.System, prompt) repeat(attempts) { - val response = llmClient.chat(messages, params) + val llmInput = messages.toList() + val response = llmClient.chat(llmInput, params) messages += ChatMessage(Role.Assistant, response) when (val result = parse(response, fileSystem)) { is ParseResult.Ok -> return ChatResult(messages, result.value) is ParseResult.Err -> { println("LLM parse failed: ${result.buildMessage()}") messages += ChatMessage(Role.User, result.buildMessage()) - onParseError(result.messages) + onParseError(result.messages, ChatExchange(llmInput, response)) } } } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/ChatExchange.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/ChatExchange.kt new file mode 100644 index 0000000..c8aaf95 --- /dev/null +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/ChatExchange.kt @@ -0,0 +1,6 @@ +package de.tuda.stg.securecoder.engine.llm + +data class ChatExchange( + val input: List, + val output: String +) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt index 016d48f..51e019e 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt @@ -1,6 +1,7 @@ package de.tuda.stg.securecoder.engine.stream import de.tuda.stg.securecoder.engine.file.edit.Changes +import de.tuda.stg.securecoder.engine.llm.ChatExchange sealed interface StreamEvent { data class SendDebugMessage(val title: String, val description: String, val icon: EventIcon) : StreamEvent @@ -11,5 +12,8 @@ sealed interface StreamEvent { data class GuardianWarning(val violations: String) : StreamEvent - data class InvalidLlmOutputWarning(val parseErrors: List) : StreamEvent + data class InvalidLlmOutputWarning( + val parseErrors: List, + val chatExchange: ChatExchange + ) : StreamEvent } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt index b3f1c67..022f270 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt @@ -41,8 +41,8 @@ class WorkflowEngine ( val out = editFiles.chat( messages = messages, fileSystem = filesystem, - onParseError = { - onEvent(StreamEvent.InvalidLlmOutputWarning(it)) + onParseError = { parseErrors, chatExchange -> + onEvent(StreamEvent.InvalidLlmOutputWarning(parseErrors, chatExchange)) } ) messages += out.changesMessage() From f672f60fbc850dcecce5925a2137dfb7ef52f72f Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Thu, 25 Dec 2025 04:17:22 +0100 Subject: [PATCH 11/43] Add debug popup for invalid LLM responses --- .../plugin/engine/event/StreamEventMapper.kt | 21 +++++++++- .../plugin/engine/event/UiStreamEvent.kt | 7 +++- .../plugin/toolwindow/EventsPanel.kt | 42 +++++++++++++++++++ .../messages/SecureCoderBundle.properties | 3 ++ 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt index 8aad468..c3341f9 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt @@ -36,11 +36,30 @@ object StreamEventMapper { UiStreamEvent.Message( title = SecureCoderBundle.message("warning.llm.title"), description = SecureCoderBundle.message("warning.llm.description", event.parseErrors.joinToString("\n")), - icon = AllIcons.General.Warning + icon = AllIcons.General.Warning, + debugText = buildExchangeText(event) ) } } + private fun buildExchangeText(event: StreamEvent.InvalidLlmOutputWarning): String { + val sb = StringBuilder() + sb.appendLine("Input messages:") + event.chatExchange.input.forEachIndexed { idx, msg -> + sb.appendLine("#${idx + 1} ${msg.role}:") + sb.appendLine(msg.content) + sb.appendLine() + } + sb.appendLine("Model output:") + sb.appendLine(event.chatExchange.output) + sb.appendLine() + sb.appendLine("Parse errors:") + event.parseErrors.forEachIndexed { index, error -> + sb.appendLine("#${index + 1}: $error") + } + return sb.toString() + } + fun mapException(exception: Exception): UiStreamEvent.Message { return UiStreamEvent.Message( title = SecureCoderBundle.message("error.uncaught.title"), diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt index 0800b0e..ae460ce 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt @@ -6,7 +6,12 @@ import de.tuda.stg.securecoder.engine.stream.EventIcon import javax.swing.Icon sealed interface UiStreamEvent { - data class Message(val title: String, val description: String, val icon: Icon) : UiStreamEvent + data class Message( + val title: String, + val description: String, + val icon: Icon, + val debugText: String? = null + ) : UiStreamEvent data class EditFiles(val changes: Changes) : UiStreamEvent } diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt index 101a645..7cd13bd 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt @@ -2,15 +2,20 @@ package de.tuda.stg.securecoder.plugin.toolwindow import com.intellij.icons.AllIcons import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.components.JBScrollPane import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI import de.tuda.stg.securecoder.plugin.edit.buildEditFilesPanel +import de.tuda.stg.securecoder.plugin.SecureCoderBundle import de.tuda.stg.securecoder.plugin.engine.IntelliJProjectFileSystem import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent import java.awt.Component import java.awt.Dimension import javax.swing.BorderFactory import javax.swing.BoxLayout +import javax.swing.JButton +import javax.swing.JComponent import javax.swing.JLabel import javax.swing.JPanel import javax.swing.JTextArea @@ -45,6 +50,21 @@ object EventsPanel { } titlePanel.add(titleLabel) + if (event is UiStreamEvent.Message && event.debugText != null) { + val debugButton = JButton(AllIcons.General.Information).apply { + border = JBUI.Borders.empty() + isContentAreaFilled = false + toolTipText = SecureCoderBundle.message("toolwindow.events.show.debug.tooltip") + addActionListener { _ -> + showDebugTextDialog(project, event.debugText) + } + } + titlePanel.add(JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + }) + titlePanel.add(debugButton) + } + val content = when (event) { is UiStreamEvent.Message -> JTextArea(event.description).apply { isEditable = false @@ -65,4 +85,26 @@ object EventsPanel { card.add(content) return card } + + private fun showDebugTextDialog(project: Project, text: String) { + object : DialogWrapper(project) { + init { + title = SecureCoderBundle.message("toolwindow.events.debug.title") + init() + setSize(800, 600) + } + + override fun createCenterPanel(): JComponent { + val area = JTextArea(text).apply { + isEditable = false + lineWrap = true + wrapStyleWord = true + border = JBUI.Borders.empty() + } + return JBScrollPane(area).apply { + border = JBUI.Borders.empty() + } + } + }.showAndGet() + } } diff --git a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties index 4fed700..0fb930c 100644 --- a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties +++ b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties @@ -53,6 +53,9 @@ toolwindow.notConfigured.title=Connect an LLM to use SecureCoder toolwindow.notConfigured.desc=No LLM provider is configured. Open settings to choose a provider or enter credentials. toolwindow.notConfigured.openSettings=Open Settings +toolwindow.events.show.debug.tooltip=Show debug +toolwindow.events.debug.title=Debug Information + codeql.alreadyInstalled=CodeQL v{0} is already installed. codeql.downloading=Downloading CodeQL CLI v{0}... codeql.installation.missingExecutable=Installation finished, but executable not found at: {0} From 494ff1b09320745c090be5de8160a6814c90d7ef Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sun, 28 Dec 2025 21:48:35 +0100 Subject: [PATCH 12/43] Add failures in GuardianExecutor result rather than throwing --- .../engine/workflow/GuardianExecutor.kt | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/GuardianExecutor.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/GuardianExecutor.kt index 0d4535d..0d90c43 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/GuardianExecutor.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/GuardianExecutor.kt @@ -21,10 +21,16 @@ class GuardianExecutor ( data class GuardianResult( val violations: List, val files: List, + val failures: List = emptyList(), ) { fun hasNoViolations() = violations.isEmpty() } + data class GuardianFailure( + val guardian: String, + val message: String, + ) + suspend fun analyze(fileSystem: FileSystem, changes: Changes): GuardianResult { val files = mutableListOf() ApplyChanges.applyAll( @@ -32,16 +38,26 @@ class GuardianExecutor ( { fileSystem.getFile(it)?.content() }, { file, content -> files.add(File(file, content)) } ) - val request = AnalyzeRequest(fileSystem, files) - val result = execute(request) - return result + return execute(AnalyzeRequest(fileSystem, files)) } private suspend fun execute(request: AnalyzeRequest): GuardianResult = coroutineScope { + data class Outcome(val violations: List, val failure: GuardianFailure?) + val deferred = guardians.map { guardian -> - async(dispatcher) { guardian.run(request) } + async(dispatcher) { + try { + val resp = guardian.run(request) + Outcome(resp.violations, null) + } catch (throwable: Throwable) { + val name = guardian.javaClass.simpleName + Outcome(emptyList(), GuardianFailure(name, throwable.message ?: throwable.toString())) + } + } } - val violations = deferred.awaitAll().flatMap { it.violations } - GuardianResult(violations, request.files) + val outcomes = deferred.awaitAll() + val violations = outcomes.flatMap { it.violations } + val failures = outcomes.mapNotNull { it.failure } + GuardianResult(violations, request.files, failures) } } From 609158430661955ffa14a107b86bf1e263b50cc7 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sun, 28 Dec 2025 21:49:11 +0100 Subject: [PATCH 13/43] Expose full GuardianExecutor result in GuardianWarningEvent --- .../stg/securecoder/plugin/engine/event/StreamEventMapper.kt | 2 +- .../stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt | 5 ++--- .../de/tuda/stg/securecoder/engine/stream/StreamEvent.kt | 3 ++- .../tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt index c3341f9..b770419 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt @@ -27,7 +27,7 @@ object StreamEventMapper { is StreamEvent.GuardianWarning -> { UiStreamEvent.Message( title = SecureCoderBundle.message("warning.guardian.title"), - description = SecureCoderBundle.message("warning.guardian.description", event.violations), + description = SecureCoderBundle.message("warning.guardian.description", event.result.violations.toString()), icon = AllIcons.General.Warning ) } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt index 6a28b25..b7d4447 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt @@ -85,9 +85,8 @@ class EditFilesLlmWrapper( val allErrors = mutableListOf() val contentCopy = if (content.endsWith("\n")) content else content + "\n" val editsRegex = Regex( - "(.*?)", setOf( - RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL - ) + "(.*?)", + setOf(RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL) ) val matches = editsRegex.findAll(contentCopy).toList() diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt index 51e019e..bb58443 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt @@ -2,6 +2,7 @@ package de.tuda.stg.securecoder.engine.stream import de.tuda.stg.securecoder.engine.file.edit.Changes import de.tuda.stg.securecoder.engine.llm.ChatExchange +import de.tuda.stg.securecoder.engine.workflow.GuardianExecutor sealed interface StreamEvent { data class SendDebugMessage(val title: String, val description: String, val icon: EventIcon) : StreamEvent @@ -10,7 +11,7 @@ sealed interface StreamEvent { data class EnrichmentWarning(val errorMessage: String) : StreamEvent - data class GuardianWarning(val violations: String) : StreamEvent + data class GuardianWarning(val result: GuardianExecutor.GuardianResult) : StreamEvent data class InvalidLlmOutputWarning( val parseErrors: List, diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt index 022f270..56be958 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt @@ -51,7 +51,7 @@ class WorkflowEngine ( if (guardianResult.hasNoViolations()) { return EngineResult.Success(changes) } - onEvent(StreamEvent.GuardianWarning(guardianResult.violations.toString())) + onEvent(StreamEvent.GuardianWarning(guardianResult)) messages += ChatMessage(Role.User, guardianResult.buildFeedbackForLlm()) } return EngineResult.Failure.ValidationFailure(maxGuardianRetries) From c162d35a6e9fc37b6bf41c0f4e63b392f8861910 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sun, 28 Dec 2025 22:00:53 +0100 Subject: [PATCH 14/43] Include raw result in parsed violation data --- .../api/src/main/java/de/tuda/stg/securecoder/guardian/Models.kt | 1 + .../main/java/de/tuda/stg/securecoder/guardian/CodeQLGuardian.kt | 1 + .../main/java/de/tuda/stg/securecoder/guardian/SarifParser.kt | 1 + 3 files changed, 3 insertions(+) diff --git a/guardian/api/src/main/java/de/tuda/stg/securecoder/guardian/Models.kt b/guardian/api/src/main/java/de/tuda/stg/securecoder/guardian/Models.kt index 780082e..01ac990 100644 --- a/guardian/api/src/main/java/de/tuda/stg/securecoder/guardian/Models.kt +++ b/guardian/api/src/main/java/de/tuda/stg/securecoder/guardian/Models.kt @@ -24,6 +24,7 @@ data class Violation( val location: Location, val hardReject: Boolean, val confidence: String? = null, + val raw: String? = null, ) @Serializable diff --git a/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/CodeQLGuardian.kt b/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/CodeQLGuardian.kt index 2657143..770af32 100644 --- a/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/CodeQLGuardian.kt +++ b/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/CodeQLGuardian.kt @@ -48,6 +48,7 @@ class CodeQLGuardian( } return AnalyzeResponse(violations) } finally { + // todo // workRoot.toFile().deleteRecursively() } } diff --git a/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/SarifParser.kt b/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/SarifParser.kt index 0724558..7a3bdce 100644 --- a/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/SarifParser.kt +++ b/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/SarifParser.kt @@ -54,6 +54,7 @@ private fun mapResultToViolation(res: JsonElement): Violation { ), hardReject = false, confidence = confidence, + raw = res.toString(), ) } From 6c470f1a4c0a381d69c1128b4ab026643dcbaa53 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sun, 28 Dec 2025 22:06:57 +0100 Subject: [PATCH 15/43] Added `parseChangesAttempts` to workflow engine configuration --- .../de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt index 56be958..79a6c93 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt @@ -19,6 +19,7 @@ class WorkflowEngine ( llmClient: LlmClient, guardians: List = emptyList(), private val maxGuardianRetries: Int = 6, + private val parseChangesAttempts: Int = 3, ) : Engine { private val promptEnrichRunner = PromptEnrichRunner(enricher) private val editFiles = EditFilesLlmWrapper(llmClient) @@ -43,7 +44,8 @@ class WorkflowEngine ( fileSystem = filesystem, onParseError = { parseErrors, chatExchange -> onEvent(StreamEvent.InvalidLlmOutputWarning(parseErrors, chatExchange)) - } + }, + attempts = parseChangesAttempts ) messages += out.changesMessage() val changes = out.changes ?: return EngineResult.Failure.GenerationFailure From 728debdcfdbecc0cec1942d972b7245c732dece1 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sun, 28 Dec 2025 23:46:04 +0100 Subject: [PATCH 16/43] Restructure guardian stream events --- .../de/tuda/stg/securecoder/benchmark/Main.kt | 11 +++++++++-- .../plugin/engine/DummyAgentStreamer.kt | 3 ++- .../plugin/engine/event/StreamEventMapper.kt | 12 +++++++++++- .../securecoder/engine/stream/ProposalId.kt | 11 +++++++++++ .../securecoder/engine/stream/StreamEvent.kt | 19 +++++++++++++++---- .../engine/workflow/WorkflowEngine.kt | 7 ++++++- .../securecoder/guardian/CodeQLGuardian.kt | 4 ++-- 7 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/ProposalId.kt diff --git a/app/benchmark-securityeval/src/main/kotlin/de/tuda/stg/securecoder/benchmark/Main.kt b/app/benchmark-securityeval/src/main/kotlin/de/tuda/stg/securecoder/benchmark/Main.kt index e103dac..ca5a77f 100644 --- a/app/benchmark-securityeval/src/main/kotlin/de/tuda/stg/securecoder/benchmark/Main.kt +++ b/app/benchmark-securityeval/src/main/kotlin/de/tuda/stg/securecoder/benchmark/Main.kt @@ -47,10 +47,12 @@ suspend fun runSecurityEval( val fs = InMemoryFileSystem() val onEvent: suspend (StreamEvent) -> Unit = { ev -> when (ev) { - is StreamEvent.EditFiles -> { + is StreamEvent.ProposedEdits -> { println(ev) fs.applyEdits(ev.changes.searchReplaces) } + is StreamEvent.ValidationStarted -> {} + is StreamEvent.ValidationSucceeded -> {} is StreamEvent.SendDebugMessage -> { if (ev.icon != EventIcon.Info) { println("ENGINE: $ev") @@ -67,7 +69,12 @@ suspend fun runSecurityEval( } } } - engine.run(item.prompt, fs, onEvent) + val result = engine.run(item.prompt, fs, onEvent) + if (result !is Engine.EngineResult.Success) { + println("Failed to generate edits for item $item: $result") + continue + } + fs.applyEdits(result.changes.searchReplaces) val files = fs.allFiles().toList() if (files.size != 1) { println("Expected 1 file, but got ${files.size}") diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/DummyAgentStreamer.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/DummyAgentStreamer.kt index 7b23fd6..7055cd8 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/DummyAgentStreamer.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/DummyAgentStreamer.kt @@ -6,6 +6,7 @@ import de.tuda.stg.securecoder.engine.file.edit.Changes import de.tuda.stg.securecoder.engine.file.edit.Changes.* import de.tuda.stg.securecoder.engine.stream.EventIcon import de.tuda.stg.securecoder.engine.stream.StreamEvent +import de.tuda.stg.securecoder.engine.stream.ProposalId import de.tuda.stg.securecoder.filesystem.FileSystem import kotlinx.coroutines.delay import java.util.concurrent.ThreadLocalRandom @@ -39,7 +40,7 @@ class DummyAgentStreamer : Engine { val icon = randomPick(EventIcon.entries) onEvent(StreamEvent.SendDebugMessage(title, desc, icon)) if (idx % 3 == 0) { - onEvent(StreamEvent.EditFiles(Changes(listOf( + onEvent(StreamEvent.ProposedEdits(ProposalId.newId(), Changes(listOf( SearchReplace("app.py", SearchedText.append(), "print(\"Hello World!\")"), SearchReplace("app2.py", SearchedText.append(), "print(\"Hello World!\")"), SearchReplace("app.py", SearchedText.append(), "\nprint(\"Hello World!2\")"), diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt index b770419..872a093 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt @@ -6,7 +6,17 @@ import de.tuda.stg.securecoder.plugin.SecureCoderBundle object StreamEventMapper { fun map(event: StreamEvent): UiStreamEvent = when (event) { - is StreamEvent.EditFiles -> UiStreamEvent.EditFiles(event.changes) + is StreamEvent.ProposedEdits -> UiStreamEvent.EditFiles(event.changes) + is StreamEvent.ValidationStarted -> UiStreamEvent.Message( + title = "Validation started", + description = "", + icon = AllIcons.General.Information + ) + is StreamEvent.ValidationSucceeded -> UiStreamEvent.Message( + title = "Validation succeeded", + description = "", + icon = AllIcons.General.InspectionsOK + ) is StreamEvent.SendDebugMessage -> { UiStreamEvent.Message( diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/ProposalId.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/ProposalId.kt new file mode 100644 index 0000000..004a7a8 --- /dev/null +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/ProposalId.kt @@ -0,0 +1,11 @@ +package de.tuda.stg.securecoder.engine.stream + +import java.util.UUID + +class ProposalId(val value: String) { + override fun toString(): String = value + + companion object { + fun newId(): ProposalId = ProposalId(UUID.randomUUID().toString()) + } +} diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt index bb58443..1f5749c 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt @@ -7,14 +7,25 @@ import de.tuda.stg.securecoder.engine.workflow.GuardianExecutor sealed interface StreamEvent { data class SendDebugMessage(val title: String, val description: String, val icon: EventIcon) : StreamEvent - data class EditFiles(val changes: Changes) : StreamEvent - data class EnrichmentWarning(val errorMessage: String) : StreamEvent - data class GuardianWarning(val result: GuardianExecutor.GuardianResult) : StreamEvent - data class InvalidLlmOutputWarning( val parseErrors: List, val chatExchange: ChatExchange ) : StreamEvent + + data class ProposedEdits( + val id: ProposalId, + val changes: Changes + ) : StreamEvent + + data class ValidationStarted( + val id: ProposalId, + ) : StreamEvent + + data class GuardianWarning(val id: ProposalId, val result: GuardianExecutor.GuardianResult) : StreamEvent + + data class ValidationSucceeded( + val id: ProposalId + ) : StreamEvent } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt index 79a6c93..be4db6e 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt @@ -9,6 +9,7 @@ import de.tuda.stg.securecoder.engine.file.edit.EditFilesLlmWrapper import de.tuda.stg.securecoder.engine.llm.FilesInContextPromptBuilder import de.tuda.stg.securecoder.engine.llm.LlmClient import de.tuda.stg.securecoder.engine.stream.StreamEvent +import de.tuda.stg.securecoder.engine.stream.ProposalId import de.tuda.stg.securecoder.engine.workflow.FeedbackBuilder.buildFeedbackForLlm import de.tuda.stg.securecoder.enricher.PromptEnricher import de.tuda.stg.securecoder.filesystem.FileSystem @@ -49,11 +50,15 @@ class WorkflowEngine ( ) messages += out.changesMessage() val changes = out.changes ?: return EngineResult.Failure.GenerationFailure + val proposalId = ProposalId.newId() + onEvent(StreamEvent.ProposedEdits(proposalId, changes)) + onEvent(StreamEvent.ValidationStarted(proposalId)) val guardianResult = guardianExecutor.analyze(filesystem, changes) if (guardianResult.hasNoViolations()) { + onEvent(StreamEvent.ValidationSucceeded(proposalId)) return EngineResult.Success(changes) } - onEvent(StreamEvent.GuardianWarning(guardianResult)) + onEvent(StreamEvent.GuardianWarning(proposalId, guardianResult)) messages += ChatMessage(Role.User, guardianResult.buildFeedbackForLlm()) } return EngineResult.Failure.ValidationFailure(maxGuardianRetries) diff --git a/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/CodeQLGuardian.kt b/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/CodeQLGuardian.kt index 770af32..e87f69e 100644 --- a/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/CodeQLGuardian.kt +++ b/guardian/codeql/src/main/java/de/tuda/stg/securecoder/guardian/CodeQLGuardian.kt @@ -6,7 +6,7 @@ import java.nio.file.Path import kotlin.io.path.exists class CodeQLGuardian( - private val codeqlBinary: String = "codeql", + private val codeQlBinary: String = "codeql", private val defaultQueryPacksByLanguage: Map = mapOf( "javascript" to "codeql/javascript-queries", "python" to "codeql/python-queries", @@ -28,7 +28,7 @@ class CodeQLGuardian( val sarifPaths = mutableListOf() println("detected languages $languages for files ${req.files.joinToString(",")}") - val runner = CodeQLRunner(codeqlBinary) + val runner = CodeQLRunner(codeQlBinary) for (language in languages) { val dbDir = workRoot.resolve("db-$language") val outSarif = workRoot.resolve("results-$language.sarif") From 518c0a55df125ba5d130538903f8222b55309858 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 29 Dec 2025 02:39:19 +0100 Subject: [PATCH 17/43] Add optional delay to DummyGuardian --- .../stg/securecoder/plugin/engine/EngineRunnerService.kt | 2 +- guardian/api/build.gradle.kts | 1 + .../java/de/tuda/stg/securecoder/guardian/DummyGuardian.kt | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/EngineRunnerService.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/EngineRunnerService.kt index 0df29c9..0696f42 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/EngineRunnerService.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/EngineRunnerService.kt @@ -52,7 +52,7 @@ class EngineRunnerService( PromptEnricher.PASSTHROUGH } val guardians = listOfNotNull( - if (settings.enableDummyGuardian) DummyGuardian() else null, + if (settings.enableDummyGuardian) DummyGuardian(sleepMillis = 2000) else null, if (settings.enableCodeQLGuardian) CodeQLGuardian(settings.codeqlBinary) else null ) diff --git a/guardian/api/build.gradle.kts b/guardian/api/build.gradle.kts index 354fec4..9a22906 100644 --- a/guardian/api/build.gradle.kts +++ b/guardian/api/build.gradle.kts @@ -10,4 +10,5 @@ base { dependencies { api(libs.kotlinx.serialization.json) api(project(":filesystem")) + implementation(libs.kotlinx.coroutines.core) } diff --git a/guardian/api/src/main/java/de/tuda/stg/securecoder/guardian/DummyGuardian.kt b/guardian/api/src/main/java/de/tuda/stg/securecoder/guardian/DummyGuardian.kt index 5d89ec2..7eac575 100644 --- a/guardian/api/src/main/java/de/tuda/stg/securecoder/guardian/DummyGuardian.kt +++ b/guardian/api/src/main/java/de/tuda/stg/securecoder/guardian/DummyGuardian.kt @@ -1,13 +1,18 @@ package de.tuda.stg.securecoder.guardian import kotlin.random.Random +import kotlinx.coroutines.delay class DummyGuardian( private val flagProbabilityPerFile: Double = 0.8, private val hardRejet: Boolean = false, private val rng: Random = Random, + private val sleepMillis: Long = 0, ) : Guardian { override suspend fun run(req: AnalyzeRequest): AnalyzeResponse { + if (sleepMillis > 0) { + delay(sleepMillis) + } val violations = req.files .filter { rng.nextDouble() < flagProbabilityPerFile } .map { randomViolationFor(it) } From 6bc013feb6245f0a32de118b9f50c235dadc475b Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:33:41 +0100 Subject: [PATCH 18/43] Redo guardian validations in the frontend --- .../plugin/engine/EngineRunnerService.kt | 7 ++- .../plugin/engine/event/EngineResultMapper.kt | 8 +-- .../plugin/engine/event/StreamEventMapper.kt | 55 +++++++++++++------ .../plugin/engine/event/UiStreamEvent.kt | 13 ++++- .../plugin/toolwindow/EventsPanel.kt | 45 +++++++++++++++ .../SecureCoderAiToolWindowFactory.kt | 21 ++++++- .../messages/SecureCoderBundle.properties | 4 ++ .../securecoder/engine/stream/StreamEvent.kt | 17 ++++-- 8 files changed, 139 insertions(+), 31 deletions(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/EngineRunnerService.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/EngineRunnerService.kt index 0696f42..fc6aca7 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/EngineRunnerService.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/EngineRunnerService.kt @@ -78,19 +78,20 @@ class EngineRunnerService( withBackgroundProgress(project, "Running engine…", cancellable = false) { val fileSystem = IntelliJProjectFileSystem(project) var handle: EngineHandle? = null + val mapper = StreamEventMapper() try { handle = buildEngine() val result = handle.engine.run( text, fileSystem, - { engineEvent -> onUiEvent(StreamEventMapper.map(engineEvent)) }, + { engineEvent -> onUiEvent(mapper.map(engineEvent)) }, buildContext(reduceContextToOpenFiles) ) - onUiEvent(EngineResultMapper.map(result)) + EngineResultMapper.map(mapper, result)?.let { onUiEvent(it) } } catch (exception: Exception) { thisLogger().error("Uncaught exception within the engine", exception) - onUiEvent(StreamEventMapper.mapException(exception)) + onUiEvent(mapper.mapException(exception)) } finally { runCatching { handle?.close?.invoke() }.onFailure { thisLogger().warn("Failed closing engine handle", it) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/EngineResultMapper.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/EngineResultMapper.kt index df16a81..07ed713 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/EngineResultMapper.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/EngineResultMapper.kt @@ -3,15 +3,15 @@ package de.tuda.stg.securecoder.plugin.engine.event import de.tuda.stg.securecoder.engine.Engine.EngineResult object EngineResultMapper { - fun map(result: EngineResult): UiStreamEvent { + fun map(mapper: StreamEventMapper, result: EngineResult): UiStreamEvent? { return when (result) { EngineResult.Failure.GenerationFailure -> { - StreamEventMapper.createGenerationError() + mapper.createGenerationError() } is EngineResult.Failure.ValidationFailure -> { - StreamEventMapper.createValidationError(result.maxGuardianRetries) + mapper.createValidationError(result.maxGuardianRetries) } - is EngineResult.Success -> UiStreamEvent.EditFiles(result.changes) + is EngineResult.Success -> null } } } diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt index 872a093..29ba23e 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt @@ -3,20 +3,31 @@ package de.tuda.stg.securecoder.plugin.engine.event import com.intellij.icons.AllIcons import de.tuda.stg.securecoder.engine.stream.StreamEvent import de.tuda.stg.securecoder.plugin.SecureCoderBundle +import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent.EditFilesValidation + +class StreamEventMapper { + private val proposals = mutableMapOf() -object StreamEventMapper { fun map(event: StreamEvent): UiStreamEvent = when (event) { - is StreamEvent.ProposedEdits -> UiStreamEvent.EditFiles(event.changes) - is StreamEvent.ValidationStarted -> UiStreamEvent.Message( - title = "Validation started", - description = "", - icon = AllIcons.General.Information - ) - is StreamEvent.ValidationSucceeded -> UiStreamEvent.Message( - title = "Validation succeeded", - description = "", - icon = AllIcons.General.InspectionsOK - ) + is StreamEvent.ProposedEdits -> { + val pid = event.id.value + val current = proposals[pid] + val merged = (current ?: UiStreamEvent.EditFiles( + changes = event.changes, + proposalId = pid, + validation = EditFilesValidation.NotAvailable, + )).copy(changes = event.changes) + proposals[pid] = merged + merged + } + is StreamEvent.ValidationStarted -> { + val pid = event.id.value + updateProposalValidation(pid, EditFilesValidation.Running) + } + is StreamEvent.ValidationSucceeded -> { + val pid = event.id.value + updateProposalValidation(pid, EditFilesValidation.Succeeded) + } is StreamEvent.SendDebugMessage -> { UiStreamEvent.Message( @@ -35,11 +46,14 @@ object StreamEventMapper { } is StreamEvent.GuardianWarning -> { - UiStreamEvent.Message( - title = SecureCoderBundle.message("warning.guardian.title"), - description = SecureCoderBundle.message("warning.guardian.description", event.result.violations.toString()), - icon = AllIcons.General.Warning - ) + val hints = event.result.violations.map { v -> + if (v.rule.name.isNullOrBlank()) v.rule.id else v.rule.name!! + } + event.result.failures.map { f -> + "Guardian '${f.guardian}' failed: ${f.message}" + } + + val pid = event.id.value + updateProposalValidation(pid, EditFilesValidation.Failed(hints)) } is StreamEvent.InvalidLlmOutputWarning -> { @@ -52,6 +66,13 @@ object StreamEventMapper { } } + private fun updateProposalValidation(pid: String, newValidation: EditFilesValidation): UiStreamEvent.EditFiles { + val current = proposals[pid] ?: throw IllegalStateException("Unknown proposal $pid") + val merged = current.copy(validation = newValidation) + proposals[pid] = merged + return merged + } + private fun buildExchangeText(event: StreamEvent.InvalidLlmOutputWarning): String { val sb = StringBuilder() sb.appendLine("Input messages:") diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt index ae460ce..6edc088 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt @@ -13,7 +13,18 @@ sealed interface UiStreamEvent { val debugText: String? = null ) : UiStreamEvent - data class EditFiles(val changes: Changes) : UiStreamEvent + data class EditFiles( + val changes: Changes, + val proposalId: String, + val validation: EditFilesValidation, + ) : UiStreamEvent + + sealed interface EditFilesValidation { + object NotAvailable : EditFilesValidation + object Running : EditFilesValidation + object Succeeded : EditFilesValidation + data class Failed(val guardianHints: List) : EditFilesValidation + } } fun EventIcon.toIntellijIcon(): Icon = when (this) { diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt index 7cd13bd..00401c8 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt @@ -10,6 +10,7 @@ import de.tuda.stg.securecoder.plugin.edit.buildEditFilesPanel import de.tuda.stg.securecoder.plugin.SecureCoderBundle import de.tuda.stg.securecoder.plugin.engine.IntelliJProjectFileSystem import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent +import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent.EditFilesValidation import java.awt.Component import java.awt.Dimension import javax.swing.BorderFactory @@ -50,6 +51,31 @@ object EventsPanel { } titlePanel.add(titleLabel) + if (event is UiStreamEvent.EditFiles) { + titlePanel.add(JPanel().apply { layout = BoxLayout(this, BoxLayout.X_AXIS) }) + val statusLabel = when(event.validation) { + is EditFilesValidation.NotAvailable -> null + is EditFilesValidation.Running -> JLabel( + SecureCoderBundle.message("validation.in_progress"), + AllIcons.Actions.Refresh, + JLabel.LEADING + ) + is EditFilesValidation.Succeeded -> JLabel( + SecureCoderBundle.message("validation.succeeded"), + AllIcons.General.InspectionsOK, + JLabel.LEADING + ) + is EditFilesValidation.Failed -> JLabel( + SecureCoderBundle.message("validation.warnings"), + AllIcons.General.Warning, + JLabel.LEADING + ) + } + if (statusLabel != null) { + titlePanel.add(statusLabel) + } + } + if (event is UiStreamEvent.Message && event.debugText != null) { val debugButton = JButton(AllIcons.General.Information).apply { border = JBUI.Borders.empty() @@ -79,9 +105,28 @@ object EventsPanel { event.changes, IntelliJProjectFileSystem(project) ) + else -> JTextArea("").apply { + isEditable = false + border = JBUI.Borders.empty() + background = card.background + alignmentX = Component.LEFT_ALIGNMENT + } } card.add(titlePanel) + if (event is UiStreamEvent.EditFiles && event.validation is EditFilesValidation.Failed) { + val hints = event.validation.guardianHints + val hintsPanel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + border = JBUI.Borders.emptyBottom(4) + alignmentX = Component.LEFT_ALIGNMENT + } + hints.forEach { hint -> + val label = JLabel("• $hint") + hintsPanel.add(label) + } + card.add(hintsPanel) + } card.add(content) return card } diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt index dee75c8..890c950 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt @@ -32,6 +32,9 @@ import javax.swing.SwingUtilities class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { private lateinit var eventsPanel: JPanel private lateinit var eventsScrollPane: JBScrollPane + private val editCardsByProposal = mutableMapOf() + + private data class EditCardState(var event: UiStreamEvent.EditFiles, var panel: JPanel) override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { val root = createRoot(project) @@ -74,6 +77,7 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { eventsPanel.removeAll() eventsPanel.revalidate() eventsPanel.repaint() + editCardsByProposal.clear() runner.runEngine( promptText, @@ -104,9 +108,22 @@ class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { event: UiStreamEvent, project: Project ) { + val existingState = (event as? UiStreamEvent.EditFiles)?.let { editCardsByProposal[it.proposalId] } + val existingIndex = existingState?.panel?.let { eventsPanel.getComponentZOrder(it) } ?: -1 val card = EventsPanel.addEventCard(event, project) - eventsPanel.add(card) - eventsPanel.add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) + + if (existingIndex >= 0 && existingState != null) { + eventsPanel.remove(existingState.panel) + eventsPanel.add(card, existingIndex) + } else { + eventsPanel.add(card) + eventsPanel.add(Box.createRigidArea(Dimension(0, JBUI.scale(8)))) + } + + if (event is UiStreamEvent.EditFiles) { + editCardsByProposal[event.proposalId] = EditCardState(event, card) + } + eventsPanel.revalidate() eventsPanel.repaint() diff --git a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties index 0fb930c..f255ce2 100644 --- a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties +++ b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties @@ -56,6 +56,10 @@ toolwindow.notConfigured.openSettings=Open Settings toolwindow.events.show.debug.tooltip=Show debug toolwindow.events.debug.title=Debug Information +validation.in_progress=Validation in progress? +validation.succeeded=Validation succeeded +validation.warnings=Validation warnings + codeql.alreadyInstalled=CodeQL v{0} is already installed. codeql.downloading=Downloading CodeQL CLI v{0}... codeql.installation.missingExecutable=Installation finished, but executable not found at: {0} diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt index 1f5749c..e07b7df 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/StreamEvent.kt @@ -2,12 +2,18 @@ package de.tuda.stg.securecoder.engine.stream import de.tuda.stg.securecoder.engine.file.edit.Changes import de.tuda.stg.securecoder.engine.llm.ChatExchange -import de.tuda.stg.securecoder.engine.workflow.GuardianExecutor +import de.tuda.stg.securecoder.engine.workflow.GuardianExecutor.GuardianResult sealed interface StreamEvent { - data class SendDebugMessage(val title: String, val description: String, val icon: EventIcon) : StreamEvent + data class SendDebugMessage( + val title: String, + val description: String, + val icon: EventIcon + ) : StreamEvent - data class EnrichmentWarning(val errorMessage: String) : StreamEvent + data class EnrichmentWarning( + val errorMessage: String + ) : StreamEvent data class InvalidLlmOutputWarning( val parseErrors: List, @@ -23,7 +29,10 @@ sealed interface StreamEvent { val id: ProposalId, ) : StreamEvent - data class GuardianWarning(val id: ProposalId, val result: GuardianExecutor.GuardianResult) : StreamEvent + data class GuardianWarning( + val id: ProposalId, + val result: GuardianResult + ) : StreamEvent data class ValidationSucceeded( val id: ProposalId From 3f3a87636acc41d623162c030fd523894c66468a Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:39:33 +0100 Subject: [PATCH 19/43] Use ProposalId instead of String --- .../plugin/engine/event/StreamEventMapper.kt | 17 +++++++---------- .../plugin/engine/event/UiStreamEvent.kt | 3 ++- .../SecureCoderAiToolWindowFactory.kt | 3 ++- .../stg/securecoder/engine/stream/ProposalId.kt | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt index 29ba23e..d94cf1c 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt @@ -1,16 +1,17 @@ package de.tuda.stg.securecoder.plugin.engine.event import com.intellij.icons.AllIcons +import de.tuda.stg.securecoder.engine.stream.ProposalId import de.tuda.stg.securecoder.engine.stream.StreamEvent import de.tuda.stg.securecoder.plugin.SecureCoderBundle import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent.EditFilesValidation class StreamEventMapper { - private val proposals = mutableMapOf() + private val proposals = mutableMapOf() fun map(event: StreamEvent): UiStreamEvent = when (event) { is StreamEvent.ProposedEdits -> { - val pid = event.id.value + val pid = event.id val current = proposals[pid] val merged = (current ?: UiStreamEvent.EditFiles( changes = event.changes, @@ -21,12 +22,10 @@ class StreamEventMapper { merged } is StreamEvent.ValidationStarted -> { - val pid = event.id.value - updateProposalValidation(pid, EditFilesValidation.Running) + updateProposalValidation(event.id, EditFilesValidation.Running) } is StreamEvent.ValidationSucceeded -> { - val pid = event.id.value - updateProposalValidation(pid, EditFilesValidation.Succeeded) + updateProposalValidation(event.id, EditFilesValidation.Succeeded) } is StreamEvent.SendDebugMessage -> { @@ -51,9 +50,7 @@ class StreamEventMapper { } + event.result.failures.map { f -> "Guardian '${f.guardian}' failed: ${f.message}" } - - val pid = event.id.value - updateProposalValidation(pid, EditFilesValidation.Failed(hints)) + updateProposalValidation(event.id, EditFilesValidation.Failed(hints)) } is StreamEvent.InvalidLlmOutputWarning -> { @@ -66,7 +63,7 @@ class StreamEventMapper { } } - private fun updateProposalValidation(pid: String, newValidation: EditFilesValidation): UiStreamEvent.EditFiles { + private fun updateProposalValidation(pid: ProposalId, newValidation: EditFilesValidation): UiStreamEvent.EditFiles { val current = proposals[pid] ?: throw IllegalStateException("Unknown proposal $pid") val merged = current.copy(validation = newValidation) proposals[pid] = merged diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt index 6edc088..954ea07 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt @@ -3,6 +3,7 @@ package de.tuda.stg.securecoder.plugin.engine.event import com.intellij.icons.AllIcons import de.tuda.stg.securecoder.engine.file.edit.Changes import de.tuda.stg.securecoder.engine.stream.EventIcon +import de.tuda.stg.securecoder.engine.stream.ProposalId import javax.swing.Icon sealed interface UiStreamEvent { @@ -15,7 +16,7 @@ sealed interface UiStreamEvent { data class EditFiles( val changes: Changes, - val proposalId: String, + val proposalId: ProposalId, val validation: EditFilesValidation, ) : UiStreamEvent diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt index 890c950..53722e0 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/SecureCoderAiToolWindowFactory.kt @@ -14,6 +14,7 @@ import com.intellij.openapi.wm.ToolWindowFactory import com.intellij.ui.components.JBScrollPane import com.intellij.ui.content.ContentFactory import com.intellij.util.ui.JBUI +import de.tuda.stg.securecoder.engine.stream.ProposalId import de.tuda.stg.securecoder.plugin.SecureCoderBundle import de.tuda.stg.securecoder.plugin.engine.EngineRunnerService import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent @@ -32,7 +33,7 @@ import javax.swing.SwingUtilities class SecureCoderAiToolWindowFactory : ToolWindowFactory, DumbAware { private lateinit var eventsPanel: JPanel private lateinit var eventsScrollPane: JBScrollPane - private val editCardsByProposal = mutableMapOf() + private val editCardsByProposal = mutableMapOf() private data class EditCardState(var event: UiStreamEvent.EditFiles, var panel: JPanel) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/ProposalId.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/ProposalId.kt index 004a7a8..43194e6 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/ProposalId.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/stream/ProposalId.kt @@ -2,7 +2,7 @@ package de.tuda.stg.securecoder.engine.stream import java.util.UUID -class ProposalId(val value: String) { +data class ProposalId(val value: String) { override fun toString(): String = value companion object { From 791c053d2cfaa4bcd9bc81c3f1886afebc48e374 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 29 Dec 2025 13:28:09 +0100 Subject: [PATCH 20/43] Present warnings more user friendly --- .../plugin/engine/event/StreamEventMapper.kt | 39 ++++++- .../plugin/engine/event/UiStreamEvent.kt | 5 +- .../plugin/toolwindow/EventsPanel.kt | 102 +++++++++--------- .../messages/SecureCoderBundle.properties | 4 +- 4 files changed, 94 insertions(+), 56 deletions(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt index d94cf1c..8428159 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt @@ -50,13 +50,15 @@ class StreamEventMapper { } + event.result.failures.map { f -> "Guardian '${f.guardian}' failed: ${f.message}" } - updateProposalValidation(event.id, EditFilesValidation.Failed(hints)) + val details = buildGuardianDetailsText(event) + val hint = hints.joinToString(" ") + updateProposalValidation(event.id, EditFilesValidation.Failed(hint, details)) } is StreamEvent.InvalidLlmOutputWarning -> { UiStreamEvent.Message( title = SecureCoderBundle.message("warning.llm.title"), - description = SecureCoderBundle.message("warning.llm.description", event.parseErrors.joinToString("\n")), + description = SecureCoderBundle.message("warning.llm.description"), icon = AllIcons.General.Warning, debugText = buildExchangeText(event) ) @@ -70,6 +72,39 @@ class StreamEventMapper { return merged } + private fun buildGuardianDetailsText(event: StreamEvent.GuardianWarning): String { + val result = event.result + val sb = StringBuilder() + if (result.violations.isNotEmpty()) { + sb.appendLine("Violations:") + result.violations.forEachIndexed { index, v -> + val ruleName = if (v.rule.name.isNullOrBlank()) v.rule.id else v.rule.name + sb.appendLine("#${index + 1} - Rule: $ruleName (id=${v.rule.id})") + sb.appendLine("Message: ${v.message}") + sb.appendLine("Location: ${v.location.file}:${v.location.startLine ?: "?"}-${v.location.endLine ?: "?"}") + v.confidence?.let { sb.appendLine("Confidence: $it") } + v.raw?.let { sb.appendLine("Raw: $it") } + sb.appendLine() + } + } else { + sb.appendLine("No violations reported.") + } + + if (result.failures.isNotEmpty()) { + sb.appendLine("Guardian Failures:") + result.failures.forEachIndexed { index, f -> + sb.appendLine("#${index + 1} - ${f.guardian}: ${f.message}") + } + sb.appendLine() + } + + if (result.files.isNotEmpty()) { + sb.appendLine("Analyzed files:") + result.files.forEach { f -> sb.appendLine("- ${f.name}") } + } + return sb.toString() + } + private fun buildExchangeText(event: StreamEvent.InvalidLlmOutputWarning): String { val sb = StringBuilder() sb.appendLine("Input messages:") diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt index 954ea07..0ba87f3 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/UiStreamEvent.kt @@ -24,7 +24,10 @@ sealed interface UiStreamEvent { object NotAvailable : EditFilesValidation object Running : EditFilesValidation object Succeeded : EditFilesValidation - data class Failed(val guardianHints: List) : EditFilesValidation + data class Failed( + val summary: String, + val details: String, + ) : EditFilesValidation } } diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt index 00401c8..1f9b46c 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/toolwindow/EventsPanel.kt @@ -3,6 +3,7 @@ package de.tuda.stg.securecoder.plugin.toolwindow import com.intellij.icons.AllIcons import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.components.IconLabelButton import com.intellij.ui.components.JBScrollPane import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI @@ -14,6 +15,7 @@ import de.tuda.stg.securecoder.plugin.engine.event.UiStreamEvent.EditFilesValida import java.awt.Component import java.awt.Dimension import javax.swing.BorderFactory +import javax.swing.Box import javax.swing.BoxLayout import javax.swing.JButton import javax.swing.JComponent @@ -51,43 +53,14 @@ object EventsPanel { } titlePanel.add(titleLabel) - if (event is UiStreamEvent.EditFiles) { - titlePanel.add(JPanel().apply { layout = BoxLayout(this, BoxLayout.X_AXIS) }) - val statusLabel = when(event.validation) { - is EditFilesValidation.NotAvailable -> null - is EditFilesValidation.Running -> JLabel( - SecureCoderBundle.message("validation.in_progress"), - AllIcons.Actions.Refresh, - JLabel.LEADING - ) - is EditFilesValidation.Succeeded -> JLabel( - SecureCoderBundle.message("validation.succeeded"), - AllIcons.General.InspectionsOK, - JLabel.LEADING - ) - is EditFilesValidation.Failed -> JLabel( - SecureCoderBundle.message("validation.warnings"), - AllIcons.General.Warning, - JLabel.LEADING - ) - } - if (statusLabel != null) { - titlePanel.add(statusLabel) - } - } - if (event is UiStreamEvent.Message && event.debugText != null) { - val debugButton = JButton(AllIcons.General.Information).apply { - border = JBUI.Borders.empty() - isContentAreaFilled = false + val debugButton = IconLabelButton( + AllIcons.General.InspectionsEye, + { showDebugTextDialog(project, event.debugText) } + ).apply { toolTipText = SecureCoderBundle.message("toolwindow.events.show.debug.tooltip") - addActionListener { _ -> - showDebugTextDialog(project, event.debugText) - } } - titlePanel.add(JPanel().apply { - layout = BoxLayout(this, BoxLayout.X_AXIS) - }) + titlePanel.add(Box.createHorizontalGlue()) titlePanel.add(debugButton) } @@ -105,29 +78,58 @@ object EventsPanel { event.changes, IntelliJProjectFileSystem(project) ) - else -> JTextArea("").apply { - isEditable = false - border = JBUI.Borders.empty() - background = card.background - alignmentX = Component.LEFT_ALIGNMENT - } } card.add(titlePanel) - if (event is UiStreamEvent.EditFiles && event.validation is EditFilesValidation.Failed) { - val hints = event.validation.guardianHints - val hintsPanel = JPanel().apply { - layout = BoxLayout(this, BoxLayout.Y_AXIS) - border = JBUI.Borders.emptyBottom(4) + card.add(content) + + if (event is UiStreamEvent.EditFiles) { + val bottomPanel = JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + border = JBUI.Borders.emptyTop(6) alignmentX = Component.LEFT_ALIGNMENT } - hints.forEach { hint -> - val label = JLabel("• $hint") - hintsPanel.add(label) + + when (val validation = event.validation) { + is EditFilesValidation.NotAvailable -> { /* no status shown */ } + is EditFilesValidation.Running -> bottomPanel.add( + JLabel( + SecureCoderBundle.message("validation.in_progress"), + AllIcons.Actions.Refresh, + JLabel.LEADING + ) + ) + is EditFilesValidation.Succeeded -> bottomPanel.add( + JLabel( + SecureCoderBundle.message("validation.succeeded"), + AllIcons.General.InspectionsOK, + JLabel.LEADING + ) + ) + is EditFilesValidation.Failed -> { + bottomPanel.add( + JLabel( + validation.summary, + AllIcons.General.Warning, + JLabel.LEADING + ) + ) + val showAllBtn = IconLabelButton( + AllIcons.General.InspectionsEye, + { showDebugTextDialog(project, validation.details) } + ).apply { + toolTipText = SecureCoderBundle.message("toolwindow.events.show.debug.tooltip") + alignmentX = Component.RIGHT_ALIGNMENT + } + bottomPanel.add(Box.createHorizontalGlue()) + bottomPanel.add(showAllBtn) + } + } + + if (bottomPanel.componentCount > 0) { + card.add(bottomPanel) } - card.add(hintsPanel) } - card.add(content) return card } diff --git a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties index f255ce2..da6a7e1 100644 --- a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties +++ b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties @@ -42,10 +42,8 @@ error.validation.title=Validation Engine: Maximum retries reached error.validation.description=The post-generation validation engine did not approve any generated output after {0} attempts. Automatic validation has been aborted. warning.enrichment.title=Automatic input augmentation failed warning.enrichment.description=The automatic input augmentation failed. The original input will be used instead. Reason: {0}. -warning.guardian.title=Validation Result: Automated Post-Generation Review -warning.guardian.description=The post-generation validation engine has reported the following violations: {0}. SecureCoder will attempt to regenerate or correct the output using these findings. warning.llm.title=Malicious or invalid LLM output -warning.llm.description=Error parsing the LLM (Large Language Model) output: {0}. The language model produced a response that could not be safely or reliably parsed. +warning.llm.description=We couldn't use the model's response this time. We're planning the next steps. toolwindow.useWholeProject=Complete Project toolwindow.useWholeProject.tooltip=When disabled, the context is reduced to currently open files. From d50112d1ad98220c132aabdd5b399a1a87cec0c8 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:00:04 +0100 Subject: [PATCH 21/43] Improve guardian summary --- .../plugin/edit/BuildEditFilesPanel.kt | 8 ++++-- .../plugin/engine/event/StreamEventMapper.kt | 27 ++++++++++++++----- .../messages/SecureCoderBundle.properties | 9 ++++--- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/edit/BuildEditFilesPanel.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/edit/BuildEditFilesPanel.kt index 5233245..c16d5d1 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/edit/BuildEditFilesPanel.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/edit/BuildEditFilesPanel.kt @@ -36,6 +36,7 @@ import de.tuda.stg.securecoder.engine.file.edit.ApplyChanges import de.tuda.stg.securecoder.engine.file.edit.ApplyChanges.applyEdits import de.tuda.stg.securecoder.filesystem.FileSystem import de.tuda.stg.securecoder.plugin.SecureCoderBundle +import com.intellij.openapi.diagnostic.Logger import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import javax.swing.BorderFactory @@ -98,12 +99,13 @@ fun buildEditFilesPanel( project ) } - } catch (t: Throwable) { + } catch (throwable: Throwable) { + LOG.error("Failed to apply edits", throwable) SwingUtilities.invokeLater { Notifications.Bus.notify( notification( SecureCoderBundle.message("edit.apply.error.title"), - t.message ?: t.toString(), + throwable.message ?: throwable.toString(), NotificationType.ERROR ), project @@ -167,6 +169,8 @@ fun buildEditFilesPanel( return panel } +private val LOG: Logger = Logger.getInstance("BuildEditFilesPanel") + private fun openDiffForFile(project: Project, fileUrl: String, edits: List) { val vfm = VirtualFileManager.getInstance() val vFile: VirtualFile? = vfm.findFileByUrl(fileUrl) diff --git a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt index 8428159..f21ac34 100644 --- a/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt +++ b/app/intellij-plugin/src/main/java/de/tuda/stg/securecoder/plugin/engine/event/StreamEventMapper.kt @@ -45,14 +45,27 @@ class StreamEventMapper { } is StreamEvent.GuardianWarning -> { - val hints = event.result.violations.map { v -> - if (v.rule.name.isNullOrBlank()) v.rule.id else v.rule.name!! - } + event.result.failures.map { f -> - "Guardian '${f.guardian}' failed: ${f.message}" + val messages = mutableListOf() + val ruleNames = event.result.violations.map { v -> + val n = v.rule.name + if (n.isNullOrBlank()) v.rule.id else n + }.distinct() + if (ruleNames.isNotEmpty()) { + messages += SecureCoderBundle.message( + "warning.guardian.short.violation", + ruleNames.joinToString { "\"$it\"" }, + ) } + if (event.result.failures.isNotEmpty()) { + messages += SecureCoderBundle.message( + "warning.guardian.short.failure", + event.result.failures.joinToString { f -> f.guardian } + ) + } + + val summary = messages.joinToString(" ") val details = buildGuardianDetailsText(event) - val hint = hints.joinToString(" ") - updateProposalValidation(event.id, EditFilesValidation.Failed(hint, details)) + updateProposalValidation(event.id, EditFilesValidation.Failed(summary, details)) } is StreamEvent.InvalidLlmOutputWarning -> { @@ -128,7 +141,7 @@ class StreamEventMapper { title = SecureCoderBundle.message("error.uncaught.title"), description = SecureCoderBundle.message( "error.uncaught.description", - exception.message ?: "N/A", + exception.message ?: SecureCoderBundle.message("common.notAvailable"), exception.javaClass.simpleName ), icon = AllIcons.General.Error diff --git a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties index da6a7e1..1126cb5 100644 --- a/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties +++ b/app/intellij-plugin/src/main/resources/messages/SecureCoderBundle.properties @@ -3,6 +3,8 @@ toolwindow.settings=Open SecureCoder Settings product.name=SecureCoder AI notification.group.name=SecureCoder AI +common.notAvailable=N/A + toolwindow.submit=Submit toolwindow.streaming=Streaming... warning.emptyPrompt=Please enter a prompt before submitting. @@ -19,7 +21,7 @@ edit.placeholder=Type your task here... edit.apply=Apply edit.apply.tooltip=Apply all pending changes -edit.apply.progress=Applying Edits... +edit.apply.progress=Applying edits... edit.apply.done.title=Changes Applied edit.apply.done.body={0} changes have been successfully applied. edit.apply.error.title=Error Applying Changes @@ -44,6 +46,8 @@ warning.enrichment.title=Automatic input augmentation failed warning.enrichment.description=The automatic input augmentation failed. The original input will be used instead. Reason: {0}. warning.llm.title=Malicious or invalid LLM output warning.llm.description=We couldn't use the model's response this time. We're planning the next steps. +warning.guardian.short.violation=May contain {0} +warning.guardian.short.failure={0} internally failed toolwindow.useWholeProject=Complete Project toolwindow.useWholeProject.tooltip=When disabled, the context is reduced to currently open files. @@ -54,9 +58,8 @@ toolwindow.notConfigured.openSettings=Open Settings toolwindow.events.show.debug.tooltip=Show debug toolwindow.events.debug.title=Debug Information -validation.in_progress=Validation in progress? +validation.in_progress=Validation in progress... validation.succeeded=Validation succeeded -validation.warnings=Validation warnings codeql.alreadyInstalled=CodeQL v{0} is already installed. codeql.downloading=Downloading CodeQL CLI v{0}... From c47b661633844b609f82cc01735e4f382038abf8 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Tue, 30 Dec 2025 18:00:17 +0100 Subject: [PATCH 22/43] Add Docker support for openapi-bridge --- app/openapi-bridge/Dockerfile | 32 ++++++++++++++++++ app/openapi-bridge/README.md | 63 ++++++++++++++++------------------- 2 files changed, 61 insertions(+), 34 deletions(-) create mode 100644 app/openapi-bridge/Dockerfile diff --git a/app/openapi-bridge/Dockerfile b/app/openapi-bridge/Dockerfile new file mode 100644 index 0000000..1660c71 --- /dev/null +++ b/app/openapi-bridge/Dockerfile @@ -0,0 +1,32 @@ +FROM eclipse-temurin:21-jdk AS builder +WORKDIR /work + +COPY . . + +RUN chmod +x ./gradlew && ./gradlew --no-daemon :app:openapi-bridge:installDist -x test + +FROM eclipse-temurin:21-jdk +ENV DEBIAN_FRONTEND=noninteractive +WORKDIR /opt + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl ca-certificates unzip \ + && rm -rf /var/lib/apt/lists/* + +ENV CODEQL_VERSION=2.16.6 +RUN mkdir -p /opt/codeql \ + && curl -L "https://github.com/github/codeql-action/releases/download/codeql-bundle-v${CODEQL_VERSION}/codeql-bundle-linux64.tar.gz" \ + -o /tmp/codeql.tgz \ + && tar -xzf /tmp/codeql.tgz -C /opt/codeql --strip-components=1 \ + && rm /tmp/codeql.tgz +ENV PATH="/opt/codeql:${PATH}" + +COPY --from=builder /work/app/openapi-bridge/build/install/openapi-bridge /opt/openapi-bridge + +ENV PORT="8080" +ENV OLLAMA_BASE_URL="http://host.docker.internal:11434" +ENV OLLAMA_KEEP_ALIVE="5m" + +EXPOSE 8080 + +ENTRYPOINT ["/opt/openapi-bridge/bin/openapi-bridge"] diff --git a/app/openapi-bridge/README.md b/app/openapi-bridge/README.md index 69cbd3a..10a1a57 100644 --- a/app/openapi-bridge/README.md +++ b/app/openapi-bridge/README.md @@ -1,55 +1,50 @@ # OpenAPI Bridge Server This module contains the HTTP server. It exposes a minimal OpenAI-style `POST /v1/chat/completions` endpoint backed by the SecureCoder engine. -## Prerequisites -- JDK 21 -- Optional but recommended for security analysis features: CodeQL CLI in `PATH` (the Guardian uses it when analyzing code). If not present, some security analysis steps may fail. +## Run with Docker +### Configuration +- OpenRouter mode: + - `OPENROUTER_KEY` — your OpenRouter API key + - `MODEL` — model ID (e.g.: `openai/gpt-oss-20b`) +- Ollama mode (used when `OPENROUTER_KEY` is not set): + - `MODEL` — Ollama model name (e.g.: `gpt-oss:20`) + - `OLLAMA_BASE_URL` — base URL to Ollama (default: 11434 on the host) + - `OLLAMA_KEEP_ALIVE` — keep-alive duration (default: `5m`) -## Configuration (Environment Variables or -D system properties) -The server reads its configuration from either environment variables or JVM system properties (`-DNAME=value`). - -- `PORT` — HTTP port (default: `8080`) -- LLM selection (EngineFactory picks the first matching provider): - - OpenRouter mode: - - `OPENROUTER_KEY` — your OpenRouter API key - - `MODEL` — model ID (default: `openai/gpt-oss-20b`) - - Ollama mode (used when `OPENROUTER_KEY` is not set): - - `MODEL` — Ollama model name, e.g. `llama3.1:8b` - - `OLLAMA_BASE_URL` — base URL to Ollama (default: `http://127.0.0.1:11434`) - - `OLLAMA_KEEP_ALIVE` — keep-alive duration (default: `5m`) - - -## How to run the server - -On macOS/Linux (using -D system properties): - +### Build and run +Make sure you have Docker installed and are in the project root directory. ``` -./gradlew :app:openapi-bridge:run -DOPENROUTER_KEY=... -DMODEL=... +docker build -f app/openapi-bridge/Dockerfile -t openapi-bridge:latest . ``` -Or with environment variables: - +Run with Ollama on the host (macOS/Windows): ``` -export OPENROUTER_KEY=... -export MODEL=... -./gradlew :app:openapi-bridge:run +docker run --rm -p 8080:8080 \ + -e MODEL="llama3.1:8b" \ + openapi-bridge:latest ``` -On Windows +Run with Ollama on the host (Linux): +``` +docker run --rm -p 8080:8080 \ + --add-host=host.docker.internal:host-gateway \ + -e MODEL="llama3.1:8b" \ + openapi-bridge:latest +``` +Run using OpenRouter instead of Ollama: ``` -set OPENROUTER_KEY=... -set MODEL=... -gradlew.bat :app:openapi-bridge:run +docker run --rm -p 8080:8080 \ + -e OPENROUTER_KEY=... \ + -e MODEL=openai/gpt-oss-20b \ + openapi-bridge:latest ``` ## Endpoint - - `POST /v1/chat/completions` — accepts a minimal OpenAI-style request and returns a single choice with the SecureCoder engine’s response. -Example request (curl): - +Example request (from host): ``` curl -X POST "http://localhost:8080/v1/chat/completions" \ -H "Content-Type: application/json" \ From e73e479115a9d6abe94f5dc70cbe2f747065c803 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Tue, 30 Dec 2025 18:01:52 +0100 Subject: [PATCH 23/43] openapi -> openai --- app/{openapi-bridge => openai-bridge}/Dockerfile | 0 app/{openapi-bridge => openai-bridge}/README.md | 0 app/{openapi-bridge => openai-bridge}/build.gradle.kts | 0 .../java/de/tuda/stg/securecoder/openapibridge/AgentService.kt | 0 .../java/de/tuda/stg/securecoder/openapibridge/EngineFactory.kt | 0 .../src/main/java/de/tuda/stg/securecoder/openapibridge/Main.kt | 0 .../java/de/tuda/stg/securecoder/openapibridge/OpenAIRoutes.kt | 0 .../java/de/tuda/stg/securecoder/openapibridge/OpenApiModels.kt | 0 settings.gradle.kts | 2 +- 9 files changed, 1 insertion(+), 1 deletion(-) rename app/{openapi-bridge => openai-bridge}/Dockerfile (100%) rename app/{openapi-bridge => openai-bridge}/README.md (100%) rename app/{openapi-bridge => openai-bridge}/build.gradle.kts (100%) rename app/{openapi-bridge => openai-bridge}/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt (100%) rename app/{openapi-bridge => openai-bridge}/src/main/java/de/tuda/stg/securecoder/openapibridge/EngineFactory.kt (100%) rename app/{openapi-bridge => openai-bridge}/src/main/java/de/tuda/stg/securecoder/openapibridge/Main.kt (100%) rename app/{openapi-bridge => openai-bridge}/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenAIRoutes.kt (100%) rename app/{openapi-bridge => openai-bridge}/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenApiModels.kt (100%) diff --git a/app/openapi-bridge/Dockerfile b/app/openai-bridge/Dockerfile similarity index 100% rename from app/openapi-bridge/Dockerfile rename to app/openai-bridge/Dockerfile diff --git a/app/openapi-bridge/README.md b/app/openai-bridge/README.md similarity index 100% rename from app/openapi-bridge/README.md rename to app/openai-bridge/README.md diff --git a/app/openapi-bridge/build.gradle.kts b/app/openai-bridge/build.gradle.kts similarity index 100% rename from app/openapi-bridge/build.gradle.kts rename to app/openai-bridge/build.gradle.kts diff --git a/app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt similarity index 100% rename from app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt diff --git a/app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/EngineFactory.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/EngineFactory.kt similarity index 100% rename from app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/EngineFactory.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/EngineFactory.kt diff --git a/app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/Main.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/Main.kt similarity index 100% rename from app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/Main.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/Main.kt diff --git a/app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenAIRoutes.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenAIRoutes.kt similarity index 100% rename from app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenAIRoutes.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenAIRoutes.kt diff --git a/app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenApiModels.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenApiModels.kt similarity index 100% rename from app/openapi-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenApiModels.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenApiModels.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 4870f80..f7c48ea 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,4 +19,4 @@ include("app:benchmark-securityeval") include("guardian:api") include("guardian:codeql") include("filesystem") -include("app:openapi-bridge") \ No newline at end of file +include("app:openai-bridge") \ No newline at end of file From 6d411d8d90498d2bc47cd801216790f356a0294d Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Tue, 30 Dec 2025 18:02:55 +0100 Subject: [PATCH 24/43] Dont inherit java properties for openai-bridge java exec task --- app/openai-bridge/build.gradle.kts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/app/openai-bridge/build.gradle.kts b/app/openai-bridge/build.gradle.kts index e73063a..e69b6f4 100644 --- a/app/openai-bridge/build.gradle.kts +++ b/app/openai-bridge/build.gradle.kts @@ -17,18 +17,3 @@ dependencies { application { mainClass.set("de.tuda.stg.securecoder.openapibridge.MainKt") } - -tasks.named("run") { - val keys = listOf( - "OPENROUTER_KEY", - "MODEL", - "OLLAMA_BASE_URL", - "OLLAMA_KEEP_ALIVE", - "PORT" - ) - keys.forEach { key -> - System.getProperty(key)?.let { value -> - systemProperty(key, value) - } - } -} From 88d708eb8659a4386206b6f0ca0b2613732ca9f8 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Tue, 30 Dec 2025 18:18:53 +0100 Subject: [PATCH 25/43] Improve error handling of OllamaClient --- .idea/gradle.xml | 6 ++++++ .idea/kotlinc.xml | 3 ++- .../tuda/stg/securecoder/engine/llm/OllamaClient.kt | 12 ++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index e90bd0e..33bcda4 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -21,13 +21,19 @@ diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 03fcfb7..90d1b4b 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,7 @@ - \ No newline at end of file diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt index 6d7fe91..61273f3 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt @@ -11,6 +11,7 @@ import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive @@ -49,6 +50,9 @@ class OllamaClient( val message: OllamaMsg ) + @Serializable + private data class OllamaError(val error: String) + override suspend fun chat( messages: List, params: GenerationParams @@ -81,6 +85,14 @@ class OllamaClient( } val body = resp.bodyAsText() println("Got llm response: $body") + if (!resp.status.isSuccess()) { + val errorMessage = try { + json.decodeFromString(body).error + } catch (_: SerializationException) { + body.ifBlank { "" } + } + throw RuntimeException("Failed to call Ollama got ${resp.status}: ${errorMessage}") + } val respObj = json.decodeFromString(body) return respObj.message.content From 8edfe63e4d1be125d92331e644f3f5897a400649 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Tue, 30 Dec 2025 18:19:49 +0100 Subject: [PATCH 26/43] Fix openai-bridge rename --- app/openai-bridge/Dockerfile | 6 +++--- app/openai-bridge/README.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/openai-bridge/Dockerfile b/app/openai-bridge/Dockerfile index 1660c71..953871a 100644 --- a/app/openai-bridge/Dockerfile +++ b/app/openai-bridge/Dockerfile @@ -3,7 +3,7 @@ WORKDIR /work COPY . . -RUN chmod +x ./gradlew && ./gradlew --no-daemon :app:openapi-bridge:installDist -x test +RUN chmod +x ./gradlew && ./gradlew --no-daemon :app:openai-bridge:installDist -x test FROM eclipse-temurin:21-jdk ENV DEBIAN_FRONTEND=noninteractive @@ -21,7 +21,7 @@ RUN mkdir -p /opt/codeql \ && rm /tmp/codeql.tgz ENV PATH="/opt/codeql:${PATH}" -COPY --from=builder /work/app/openapi-bridge/build/install/openapi-bridge /opt/openapi-bridge +COPY --from=builder /work/app/openai-bridge/build/install/openai-bridge /opt/openai-bridge ENV PORT="8080" ENV OLLAMA_BASE_URL="http://host.docker.internal:11434" @@ -29,4 +29,4 @@ ENV OLLAMA_KEEP_ALIVE="5m" EXPOSE 8080 -ENTRYPOINT ["/opt/openapi-bridge/bin/openapi-bridge"] +ENTRYPOINT ["/opt/openai-bridge/bin/openai-bridge"] diff --git a/app/openai-bridge/README.md b/app/openai-bridge/README.md index 10a1a57..074eb4a 100644 --- a/app/openai-bridge/README.md +++ b/app/openai-bridge/README.md @@ -15,22 +15,22 @@ This module contains the HTTP server. It exposes a minimal OpenAI-style `POST /v ### Build and run Make sure you have Docker installed and are in the project root directory. ``` -docker build -f app/openapi-bridge/Dockerfile -t openapi-bridge:latest . +docker build -f app/openai-bridge/Dockerfile -t openai-bridge:latest . ``` Run with Ollama on the host (macOS/Windows): ``` docker run --rm -p 8080:8080 \ - -e MODEL="llama3.1:8b" \ - openapi-bridge:latest + -e MODEL="gpt-oss:20b" \ + openai-bridge:latest ``` Run with Ollama on the host (Linux): ``` docker run --rm -p 8080:8080 \ --add-host=host.docker.internal:host-gateway \ - -e MODEL="llama3.1:8b" \ - openapi-bridge:latest + -e MODEL="gpt-oss:20b" \ + openai-bridge:latest ``` Run using OpenRouter instead of Ollama: @@ -38,7 +38,7 @@ Run using OpenRouter instead of Ollama: docker run --rm -p 8080:8080 \ -e OPENROUTER_KEY=... \ -e MODEL=openai/gpt-oss-20b \ - openapi-bridge:latest + openai-bridge:latest ``` ## Endpoint From 04a4515d8ace9f454b9e48b66e4e67428722068d Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Tue, 30 Dec 2025 18:39:16 +0100 Subject: [PATCH 27/43] Add in the prompt to only create ONE file when using openai bridge --- .../java/de/tuda/stg/securecoder/openapibridge/AgentService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt index 92bd6c9..cf1fd24 100644 --- a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt +++ b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt @@ -14,7 +14,7 @@ class AgentService(private val engine: Engine) { val fileSystem = InMemoryFileSystem() val userPrompt = messages.lastOrNull { it.role == "user" }?.content ?: "" val result = engine.run( - prompt = userPrompt, + prompt = "$userPrompt\nOnly create ONE file!", filesystem = fileSystem, onEvent = { event -> println("Internal Agent Event: $event") From d0281d2965c31765e1f9669e2f932664d19a18e1 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Tue, 30 Dec 2025 18:39:58 +0100 Subject: [PATCH 28/43] Rename openai bridge package --- app/openai-bridge/build.gradle.kts | 2 +- .../securecoder/{openapibridge => openaibridge}/AgentService.kt | 2 +- .../{openapibridge => openaibridge}/EngineFactory.kt | 2 +- .../stg/securecoder/{openapibridge => openaibridge}/Main.kt | 2 +- .../securecoder/{openapibridge => openaibridge}/OpenAIRoutes.kt | 2 +- .../{openapibridge => openaibridge}/OpenApiModels.kt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename app/openai-bridge/src/main/java/de/tuda/stg/securecoder/{openapibridge => openaibridge}/AgentService.kt (97%) rename app/openai-bridge/src/main/java/de/tuda/stg/securecoder/{openapibridge => openaibridge}/EngineFactory.kt (96%) rename app/openai-bridge/src/main/java/de/tuda/stg/securecoder/{openapibridge => openaibridge}/Main.kt (94%) rename app/openai-bridge/src/main/java/de/tuda/stg/securecoder/{openapibridge => openaibridge}/OpenAIRoutes.kt (89%) rename app/openai-bridge/src/main/java/de/tuda/stg/securecoder/{openapibridge => openaibridge}/OpenApiModels.kt (94%) diff --git a/app/openai-bridge/build.gradle.kts b/app/openai-bridge/build.gradle.kts index e69b6f4..140ac01 100644 --- a/app/openai-bridge/build.gradle.kts +++ b/app/openai-bridge/build.gradle.kts @@ -15,5 +15,5 @@ dependencies { } application { - mainClass.set("de.tuda.stg.securecoder.openapibridge.MainKt") + mainClass.set("de.tuda.stg.securecoder.openaibridge.MainKt") } diff --git a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/AgentService.kt similarity index 97% rename from app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/AgentService.kt index cf1fd24..31a6d09 100644 --- a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/AgentService.kt +++ b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/AgentService.kt @@ -1,4 +1,4 @@ -package de.tuda.stg.securecoder.openapibridge +package de.tuda.stg.securecoder.openaibridge import de.tuda.stg.securecoder.engine.Engine import de.tuda.stg.securecoder.engine.file.edit.ApplyChanges.applyEdits diff --git a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/EngineFactory.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/EngineFactory.kt similarity index 96% rename from app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/EngineFactory.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/EngineFactory.kt index 0c1adf4..577518b 100644 --- a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/EngineFactory.kt +++ b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/EngineFactory.kt @@ -1,4 +1,4 @@ -package de.tuda.stg.securecoder.openapibridge +package de.tuda.stg.securecoder.openaibridge import de.tuda.stg.securecoder.engine.Engine import de.tuda.stg.securecoder.engine.llm.LlmClient diff --git a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/Main.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/Main.kt similarity index 94% rename from app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/Main.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/Main.kt index 7336b9d..b3479ed 100644 --- a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/Main.kt +++ b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/Main.kt @@ -1,4 +1,4 @@ -package de.tuda.stg.securecoder.openapibridge +package de.tuda.stg.securecoder.openaibridge import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.install diff --git a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenAIRoutes.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/OpenAIRoutes.kt similarity index 89% rename from app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenAIRoutes.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/OpenAIRoutes.kt index a1e69f4..6f286a8 100644 --- a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenAIRoutes.kt +++ b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/OpenAIRoutes.kt @@ -1,4 +1,4 @@ -package de.tuda.stg.securecoder.openapibridge +package de.tuda.stg.securecoder.openaibridge import io.ktor.server.request.* import io.ktor.server.response.* diff --git a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenApiModels.kt b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/OpenApiModels.kt similarity index 94% rename from app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenApiModels.kt rename to app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/OpenApiModels.kt index fbc7f36..0656bb0 100644 --- a/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openapibridge/OpenApiModels.kt +++ b/app/openai-bridge/src/main/java/de/tuda/stg/securecoder/openaibridge/OpenApiModels.kt @@ -1,4 +1,4 @@ -package de.tuda.stg.securecoder.openapibridge +package de.tuda.stg.securecoder.openaibridge import kotlinx.serialization.Serializable From 9e479e7e124997e5c9b3fac7d3aa8c40771b5e8d Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Thu, 1 Jan 2026 18:21:28 +0100 Subject: [PATCH 29/43] Add debug logging to llm clients --- .../de/tuda/stg/securecoder/engine/llm/OllamaClient.kt | 6 ++++-- .../de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt index 61273f3..ca94d65 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt @@ -16,12 +16,14 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.buildJsonObject +import org.slf4j.LoggerFactory class OllamaClient( private val model: String, baseUrl: String = "http://127.0.0.1:11434", private val keepAlive: String = "5m", ) : LlmClient { + private val logger = LoggerFactory.getLogger("OllamaClient") private val json: Json = Json { ignoreUnknownKeys = true explicitNulls = false @@ -77,14 +79,14 @@ class OllamaClient( options = options, keepAlive = keepAlive ) - println("Sending llm request: $req") + logger.debug("Sending llm request: {}", req) val resp = http.post(endpoint) { contentType(ContentType.Application.Json) accept(ContentType.Application.Json) setBody(req) } val body = resp.bodyAsText() - println("Got llm response: $body") + logger.debug("Got llm response: {}", body) if (!resp.status.isSuccess()) { val errorMessage = try { json.decodeFromString(body).error diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt index abef7da..87e3335 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt @@ -20,12 +20,14 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject +import org.slf4j.LoggerFactory class OpenRouterClient ( private val apiKey: String, private val model: String, private val siteName: String? = null, ) : LlmClient { + private val logger = LoggerFactory.getLogger("OpenRouterClient") private val json: Json = Json { ignoreUnknownKeys = true explicitNulls = false @@ -76,6 +78,7 @@ class OpenRouterClient ( maxTokens = params.maxTokens ) + logger.debug("Sending llm request: {}", req) val resp: HttpResponse = http.post(endpoint) { contentType(ContentType.Application.Json) accept(ContentType.Application.Json) @@ -88,8 +91,7 @@ class OpenRouterClient ( if (!resp.status.isSuccess()) { error("OpenRouter Error ${resp.status.value}: $body") } - - println("OpenRouter response: $body") + logger.debug("Got llm response: {}", body) val obj = json.decodeFromString(body) val content = obj.choices.firstOrNull()?.message?.content ?: error("OpenRouter lieferte keine Antwortnachricht.") From 504a14e1d399732ae32aded7070e4e3a7d46b1ae Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Fri, 2 Jan 2026 01:12:54 +0100 Subject: [PATCH 30/43] Improved response handling for smaller llms --- .../engine/file/edit/ApplyChanges.kt | 12 ++++-- .../engine/file/edit/EditFilesLlmWrapper.kt | 43 ++++++++++++++++--- .../securecoder/engine/file/edit/Matcher.kt | 31 +++++++++---- .../stg/securecoder/engine/llm/ChatMessage.kt | 2 +- .../engine/llm/OpenRouterClient.kt | 10 ++++- .../engine/workflow/WorkflowEngine.kt | 2 +- 6 files changed, 79 insertions(+), 21 deletions(-) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/ApplyChanges.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/ApplyChanges.kt index 23e459b..f1c216c 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/ApplyChanges.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/ApplyChanges.kt @@ -21,6 +21,12 @@ object ApplyChanges { return when (match) { is Success.Append -> original + action.replaceText is Success.Match -> buildString { + if (match.end > original.length) { + throw IllegalStateException( + "Match end index (${match.end}) is out of bounds for string of length ${original.length}. " + + "Range: [${match.start}, ${match.end}), Replacement: '${action.replaceText}'" + ) + } append(original, 0, match.start) append(action.replaceText) append(original, match.end, original.length) @@ -41,7 +47,7 @@ object ApplyChanges { } } - fun match(text: String, search: Changes.SearchedText): MatchResult { + fun match(text: String?, search: Changes.SearchedText): MatchResult { return Matcher.RootMatcher.match(text, search) } @@ -50,8 +56,8 @@ object ApplyChanges { val headerMessage: String = when (matchResult) { is Error.NoMatch -> "your *SEARCH* pattern not found in file $file" - is Error.ReplaceOnEmpty -> - "can only append to file $file as it is empty or does not exist but your *SEARCH* pattern is not empty" + is Error.ReplaceOnNotExistent -> + "can only append to file $file as it is does not exist but your *SEARCH* pattern is not empty" is Error.MultipleMatch -> "your *SEARCH* pattern has several matches in $file" } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt index b7d4447..24191a0 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt @@ -6,6 +6,9 @@ import de.tuda.stg.securecoder.engine.llm.ChatMessage.Role import de.tuda.stg.securecoder.engine.llm.LlmClient import de.tuda.stg.securecoder.filesystem.FileSystem import de.tuda.stg.securecoder.engine.llm.ChatExchange +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList import kotlin.collections.plusAssign class EditFilesLlmWrapper( @@ -45,7 +48,7 @@ class EditFilesLlmWrapper( attempts: Int = 3 ): ChatResult { val messages = messages.toMutableList() - messages += ChatMessage(Role.System, prompt) + appendPromptToLastSystem(messages) repeat(attempts) { val llmInput = messages.toList() val response = llmClient.chat(llmInput, params) @@ -53,7 +56,6 @@ class EditFilesLlmWrapper( when (val result = parse(response, fileSystem)) { is ParseResult.Ok -> return ChatResult(messages, result.value) is ParseResult.Err -> { - println("LLM parse failed: ${result.buildMessage()}") messages += ChatMessage(Role.User, result.buildMessage()) onParseError(result.messages, ChatExchange(llmInput, response)) } @@ -91,7 +93,24 @@ class EditFilesLlmWrapper( val matches = editsRegex.findAll(contentCopy).toList() if (matches.isEmpty()) { - allErrors += "Did not find any *SEARCH/REPLACE* block within the `` tag" + allErrors += """ + Could not find any edit blocks in the response. + Example for the expected format: + + src/Main.java + + ...exact old text... + + + ...new text... + + + + src/new.java + + append + + """.trimIndent() return ParseResult.Err(allErrors) } @@ -123,7 +142,7 @@ class EditFilesLlmWrapper( continue } val replace = Changes.SearchReplace(currentFileName, SearchedText(searchPart ?: ""), replacePart ?: "") - val content = fileSystem.getFile(currentFileName)?.content() ?: "" + val content = fileSystem.getFile(currentFileName)?.content() val match = ApplyChanges.match(content, replace.searchedText) if (match is Matcher.MatchResult.Error) { allErrors += ApplyChanges.buildErrorMessage(currentFileName, searchPart ?: "", match) @@ -145,7 +164,10 @@ class EditFilesLlmWrapper( } private fun getTextByXMLTag(container: String, tag: String): String? { - val regex = Regex("<$tag>(.*?)", setOf(RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL)) + val regex = Regex( + "<$tag>(.*?)", + setOf(RegexOption.MULTILINE, RegexOption.DOT_MATCHES_ALL) + ) return regex.find(container)?.groups?.get(1)?.value } @@ -153,4 +175,15 @@ class EditFilesLlmWrapper( if (content == null) return null return content.replaceFirst(Regex("^\\n"), "") } + + private fun appendPromptToLastSystem(messages: MutableList) { + val lastSystemIndex = messages.indexOfLast { it.role == Role.System } + if (lastSystemIndex >= 0) { + val existing = messages[lastSystemIndex] + val combined = "${existing.content}\n\n$prompt" + messages[lastSystemIndex] = ChatMessage(Role.System, combined) + } else { + messages += ChatMessage(Role.System, prompt) + } + } } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/Matcher.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/Matcher.kt index 445620a..b442e7b 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/Matcher.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/Matcher.kt @@ -6,25 +6,31 @@ import de.tuda.stg.securecoder.engine.file.edit.Matcher.MatchResult.Success interface Matcher { sealed interface MatchResult { sealed interface Error : MatchResult { - object ReplaceOnEmpty : Error + object ReplaceOnNotExistent : Error object NoMatch : Error data class MultipleMatch (val matches: List) : Error } sealed interface Success : MatchResult { object Append : Success - class Match (val start: Int, val end: Int) : Success + class Match (val start: Int, val end: Int) : Success { + init { + require(end >= start) { + "End index ($end) cannot be less than start index ($start)" + } + } + } } } - fun match(text: String, search: Changes.SearchedText): MatchResult + fun match(text: String?, search: Changes.SearchedText): MatchResult object RootMatcher : Matcher { - override fun match(text: String, search: Changes.SearchedText): MatchResult { + override fun match(text: String?, search: Changes.SearchedText): MatchResult { if (search.isAppend()) { return Success.Append } - if (text.isEmpty()) { - return Error.ReplaceOnEmpty + if (text == null) { + return Error.ReplaceOnNotExistent } val matchers = listOf(IndexOfMatcher, TrimmedLinesMatcher) matchers.forEach { @@ -37,9 +43,12 @@ interface Matcher { object IndexOfMatcher : Matcher { override fun match( - text: String, + text: String?, search: Changes.SearchedText ): MatchResult { + if (text == null) { + return Error.NoMatch + } val idx = text.indexesOf(search.text) return when (idx.size) { 1 -> Success.Match(idx.first(), idx.first() + search.text.length) @@ -63,7 +72,10 @@ interface Matcher { } object TrimmedLinesMatcher : Matcher { - override fun match(text: String, search: Changes.SearchedText): MatchResult { + override fun match(text: String?, search: Changes.SearchedText): MatchResult { + if (text == null) { + return Error.NoMatch + } val searchedTrimmed = splitToLinesAndTrimLast(search.text).map { it.trim() } val documentLines = text.split("\n") val documentLinesTrimmed = documentLines.map { it.trim() } @@ -81,7 +93,8 @@ interface Matcher { val lineStarts = IntArray(documentLines.size + 1) for (i in documentLines.indices) { - lineStarts[i + 1] = lineStarts[i] + documentLines[i].length + 1 + val newline = if (i < documentLines.lastIndex) 1 else 0 + lineStarts[i + 1] = lineStarts[i] + documentLines[i].length + newline } val startOffset = lineStarts[firstLine] diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/ChatMessage.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/ChatMessage.kt index 6f92101..81668c4 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/ChatMessage.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/ChatMessage.kt @@ -1,5 +1,5 @@ package de.tuda.stg.securecoder.engine.llm -class ChatMessage (val role: Role, val content: String) { +data class ChatMessage (val role: Role, val content: String) { enum class Role { System, User, Assistant } } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt index 87e3335..cfa05b5 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt @@ -18,6 +18,7 @@ import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import kotlinx.serialization.SerializationException import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject import org.slf4j.LoggerFactory @@ -92,9 +93,14 @@ class OpenRouterClient ( error("OpenRouter Error ${resp.status.value}: $body") } logger.debug("Got llm response: {}", body) - val obj = json.decodeFromString(body) + val obj = try { + json.decodeFromString(body) + } catch (e: SerializationException) { + val formattedBody = body.ifBlank { "" } + throw RuntimeException("Failed to parse OpenRouter response body. Raw body: $formattedBody", e) + } val content = obj.choices.firstOrNull()?.message?.content - ?: error("OpenRouter lieferte keine Antwortnachricht.") + ?: error("OpenRouter did not return any response choices ") return content } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt index be4db6e..668108c 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt @@ -20,7 +20,7 @@ class WorkflowEngine ( llmClient: LlmClient, guardians: List = emptyList(), private val maxGuardianRetries: Int = 6, - private val parseChangesAttempts: Int = 3, + private val parseChangesAttempts: Int = 5, ) : Engine { private val promptEnrichRunner = PromptEnrichRunner(enricher) private val editFiles = EditFilesLlmWrapper(llmClient) From 5ae891d63fbdea6e0205b63347dbba10ade6ea4c Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Fri, 2 Jan 2026 02:41:55 +0100 Subject: [PATCH 31/43] Release plugin 0.0.3 --- app/intellij-plugin/src/main/resources/META-INF/plugin.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/intellij-plugin/src/main/resources/META-INF/plugin.xml b/app/intellij-plugin/src/main/resources/META-INF/plugin.xml index b9ed749..ff76646 100644 --- a/app/intellij-plugin/src/main/resources/META-INF/plugin.xml +++ b/app/intellij-plugin/src/main/resources/META-INF/plugin.xml @@ -2,7 +2,7 @@ de.tuda.securecoder SecureCoder Software Technology Group at TU Darmstadt - 0.0.2 + 0.0.3 From 309a01b620c732280404a3fdac2ec7eb9cbf4a54 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:31:04 +0100 Subject: [PATCH 32/43] Fix grammar in FilesInContextPromptBuilder --- .../stg/securecoder/engine/llm/FilesInContextPromptBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/FilesInContextPromptBuilder.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/FilesInContextPromptBuilder.kt index b073204..b3baf65 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/FilesInContextPromptBuilder.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/FilesInContextPromptBuilder.kt @@ -9,7 +9,7 @@ object FilesInContextPromptBuilder { appendLine("You have no files in the context.") appendLine("If you saw files they are only part of the prompt and dont exists yet!") if (edit) { - appendLine("You may create new files (keep in mind that searchedText needs to be empty in this case!)") + appendLine("You may create new files (keep in mind that searched text needs to be empty in this case!)") } return@buildString } From 89da0c7c233b9f7446dd515cae72d1fabc5aa141 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:39:15 +0100 Subject: [PATCH 33/43] Add EngineLlmReplayTests --- .../securecoder/engine/llm/LlmReplayModels.kt | 24 +++ .../engine/llm/LoggingAndReplayClients.kt | 67 +++++++++ .../engine/workflow/EngineLlmReplayTests.kt | 139 ++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LlmReplayModels.kt create mode 100644 engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LoggingAndReplayClients.kt create mode 100644 engine/src/test/kotlin/de/tuda/stg/securecoder/engine/workflow/EngineLlmReplayTests.kt diff --git a/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LlmReplayModels.kt b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LlmReplayModels.kt new file mode 100644 index 0000000..e4314d2 --- /dev/null +++ b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LlmReplayModels.kt @@ -0,0 +1,24 @@ +package de.tuda.stg.securecoder.engine.llm + +import kotlinx.serialization.Serializable + +@Serializable +data class LoggedCall( + val requestHash: String, + val request: String, + val params: String, + val response: String, +) + +@Serializable +data class LoggedRun( + val modelName: String, + val promptKind: String, + val runIndex: Int, + val enginePrompt: String, + val initialFiles: Map, + val calls: List, +) + +@Serializable +data class LoggedSuite(val runs: List) diff --git a/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LoggingAndReplayClients.kt b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LoggingAndReplayClients.kt new file mode 100644 index 0000000..3978cdc --- /dev/null +++ b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LoggingAndReplayClients.kt @@ -0,0 +1,67 @@ +package de.tuda.stg.securecoder.engine.llm + +import java.security.MessageDigest + +class LoggingLlmClient( + private val delegate: LlmClient +) : LlmClient { + private val _calls = mutableListOf() + val calls: List get() = _calls + + override suspend fun chat( + messages: List, + params: LlmClient.GenerationParams + ): String { + val hash = computeHash(messages, params) + val response = delegate.chat(messages, params) + _calls += LoggedCall( + requestHash = hash, + request = messages.toString(), + params = params.toString(), + response = response, + ) + return response + } + + override fun close() = delegate.close() + + companion object Commons { + fun computeHash( + messages: List, + params: LlmClient.GenerationParams + ): String { + val payload = messages.toString() + params.toString() + val bytes = MessageDigest.getInstance("SHA-256").digest(payload.toByteArray(Charsets.UTF_8)) + return bytes.toHexString() + } + } +} + +class ReplayLlmClient( + private val loggedCalls: List +) : LlmClient { + private var idx = 0 + + override suspend fun chat( + messages: List, + params: LlmClient.GenerationParams + ): String { + if (idx >= loggedCalls.size) error("ReplayLlmClient: no more calls to replay (idx=$idx, size=${loggedCalls.size})") + val calculated = LoggingLlmClient.computeHash(messages, params) + val expected = loggedCalls[idx] + if (calculated != expected.requestHash) { + throw IllegalStateException( + buildString { + appendLine("ReplayLlmClient: input hash mismatch at call #$idx") + appendLine("expected: ${expected.requestHash}") + appendLine("actual: $calculated") + appendLine("expected (m): ${expected.request}") + appendLine("actual (m): $messages") + } + ) + } + return loggedCalls[idx++].response + } + + override fun close() {} +} diff --git a/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/workflow/EngineLlmReplayTests.kt b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/workflow/EngineLlmReplayTests.kt new file mode 100644 index 0000000..12965cc --- /dev/null +++ b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/workflow/EngineLlmReplayTests.kt @@ -0,0 +1,139 @@ +package de.tuda.stg.securecoder.engine.workflow + +import de.tuda.stg.securecoder.engine.Engine.EngineResult +import de.tuda.stg.securecoder.engine.llm.* +import de.tuda.stg.securecoder.engine.stream.StreamEvent +import de.tuda.stg.securecoder.enricher.PromptEnricher +import de.tuda.stg.securecoder.filesystem.InMemoryFileSystem +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlin.test.Test +import java.nio.file.Files +import java.nio.file.Path + +class EngineLlmReplayTests { + private val json = Json { prettyPrint = true; ignoreUnknownKeys = true; encodeDefaults = true } + private val logsPath: Path = Path.of("build", "llm_logs", "log.json") + + @Test + fun generator_collects_real_llm_responses() = runBlocking { + Files.createDirectories(logsPath.parent) + + val prompts = listOf( + "create_project" to "Create a minimal Hello World project with one file Main.java printing Hello", + "edit_project" to "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + ) + + val models = buildList { + val apiKey = System.getenv("API_KEy") ?: "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" + add("OR:llama3.2:latest" to OpenRouterClient(apiKey, "meta-llama/llama-3.2-3b-instruct")) + add("OR:gpt-oss:20b" to OpenRouterClient(apiKey, "openai/gpt-oss-20b")) + //val olBase = System.getenv("OLLAMA_URL") ?: "http://127.0.0.1:11434" + //add("ollama:llama3.2:latest" to OllamaClient("llama3.2:latest", baseUrl = olBase)) + //add("ollama:gpt-oss:20b" to OllamaClient("gpt-oss:20b", baseUrl = olBase)) + } + + val runs = mutableListOf() + for ((modelName, rawClient) in models) { + rawClient.use { base -> + for ((kind, userPrompt) in prompts) { + repeat(15) { idx -> + println("Recording #${idx + 1} for $kind on $modelName") + val logging = LoggingLlmClient(base) + val fs = InMemoryFileSystem() + val initialFiles = when (kind) { + "edit_project" -> mapOf( + "src/Main.java" to """ + public class Main { + public static void main(String[] args) { + System.out.println("Hello"); + } + } + """.trimIndent() + ) + else -> emptyMap() + } + for ((name, content) in initialFiles) fs.upsert(name, content) + + val engine = WorkflowEngine( + enricher = PromptEnricher.PASSTHROUGH, + llmClient = logging, + guardians = emptyList(), + ) + engine.run(userPrompt, fs, onEvent = { /* ignore */ }, context = null) + if (logging.calls.isEmpty()) throw IllegalStateException("No calls recorded!") + runs += LoggedRun( + modelName = modelName, + promptKind = kind, + runIndex = idx, + enginePrompt = userPrompt, + initialFiles = initialFiles, + calls = logging.calls.toList(), + ) + } + } + } + } + val suite = LoggedSuite(runs) + Files.writeString(logsPath, json.encodeToString(suite)) + println("Wrote logs to $logsPath") + } + + @Test + fun replay_test_uses_recorded_responses_and_counts_success() = runBlocking { + if (!Files.exists(logsPath)) { + println("No log file at $logsPath; nothing to replay. Test will be a no-op.") + return@runBlocking + } + val suite = json.decodeFromString(Files.readString(logsPath)) + + data class Group( + val modelName: String, + val promptKind: String, + var total: Int = 0, + var successes: Int = 0, + var parseFails: Int = 0 + ) + + suite.runs + .groupBy { it.modelName to it.promptKind } + .map { (key, runs) -> + val (model, kind) = key + val group = Group(model, kind) + for (run in runs) { + val fs = InMemoryFileSystem() + for ((name, content) in run.initialFiles) fs.upsert(name, content) + + val client = ReplayLlmClient(run.calls) + val engine = WorkflowEngine( + PromptEnricher.PASSTHROUGH, + client, + guardians = emptyList(), + ) + group.total++ + var l = 0 + val result = engine.run(run.enginePrompt, fs, onEvent = { + if (it !is StreamEvent.InvalidLlmOutputWarning) return@run + group.parseFails++ + if (l++ >= 2) { + println("=======ERROR=======") + println("=======ERROR=======") + println("=======ERROR=======") + println(it.parseErrors.joinToString("\n")) + println() + println() + println(it.chatExchange.output) + } + }) + if (result is EngineResult.Success) { + group.successes++ + } + } + group + } + .forEach { + println("Group ${it.modelName} / ${it.promptKind}: replayed runs: ${it.total}, successes: ${it.successes} (${it.parseFails} parse failures)") + } + } +} From 4c70332a2dfc6a57d40c211dcc304752c37ec1d1 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:40:34 +0100 Subject: [PATCH 34/43] blocks -> edit blocks --- .../stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt index 24191a0..741bcae 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt @@ -76,7 +76,7 @@ class EditFilesLlmWrapper( appendLine("It violated the required format.") appendLine("Errors:") messages.forEach { appendLine(it) } - appendLine("Respond again with ONLY blocks that strictly follow the rules. Do NOT include prose, markdown, or explanations.") + appendLine("Respond again with ONLY edit blocks that strictly follow the rules. Do NOT include prose, markdown, or explanations.") appendLine("IMPORTANT: Resend the COMPLETE set of edits you intend to apply from your previous message") } } From 2055fff843c292456d199dd043a053557efcaa62 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:40:52 +0100 Subject: [PATCH 35/43] Move FilesInContextPromptBuilder to file package --- .../engine/{llm => file}/FilesInContextPromptBuilder.kt | 5 ++--- .../stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt | 3 --- 2 files changed, 2 insertions(+), 6 deletions(-) rename engine/src/main/kotlin/de/tuda/stg/securecoder/engine/{llm => file}/FilesInContextPromptBuilder.kt (94%) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/FilesInContextPromptBuilder.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/FilesInContextPromptBuilder.kt similarity index 94% rename from engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/FilesInContextPromptBuilder.kt rename to engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/FilesInContextPromptBuilder.kt index b3baf65..808cc0b 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/FilesInContextPromptBuilder.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/FilesInContextPromptBuilder.kt @@ -1,8 +1,7 @@ -package de.tuda.stg.securecoder.engine.llm +package de.tuda.stg.securecoder.engine.file import de.tuda.stg.securecoder.filesystem.FileSystem - object FilesInContextPromptBuilder { suspend fun build(files: Iterable, edit: Boolean = false) = buildString { if (files.count() == 0) { @@ -20,4 +19,4 @@ object FilesInContextPromptBuilder { appendLine("<<>>") } } -} +} \ No newline at end of file diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt index 741bcae..c39ad4b 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt @@ -6,9 +6,6 @@ import de.tuda.stg.securecoder.engine.llm.ChatMessage.Role import de.tuda.stg.securecoder.engine.llm.LlmClient import de.tuda.stg.securecoder.filesystem.FileSystem import de.tuda.stg.securecoder.engine.llm.ChatExchange -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.toList import kotlin.collections.plusAssign class EditFilesLlmWrapper( From 4a9860db8f607876eeb5019044c19c34cb709d06 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:48:21 +0100 Subject: [PATCH 36/43] Dont dedupe llm changes --- .../securecoder/engine/file/edit/EditFilesLlmWrapper.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt index c39ad4b..583622c 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/EditFilesLlmWrapper.kt @@ -152,12 +152,7 @@ class EditFilesLlmWrapper( return ParseResult.Err(allErrors) } - val seen = HashSet>() - val deduped = results.filter { sr -> - seen.add(sr.fileName to sr.searchedText.text) - } - - return ParseResult.Ok(Changes(deduped)) + return ParseResult.Ok(Changes(results)) } private fun getTextByXMLTag(container: String, tag: String): String? { From 31d81e8f050016d565545f4c75b1792ce9a9f5dd Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sat, 3 Jan 2026 16:53:34 +0100 Subject: [PATCH 37/43] Basic structured output support --- .../engine/llm/KxJsonSchemaFormat.kt | 82 +++++++++++++++++++ .../stg/securecoder/engine/llm/LlmClient.kt | 14 ++++ .../securecoder/engine/llm/OllamaClient.kt | 74 ++++++++++++----- .../engine/llm/OpenRouterClient.kt | 82 +++++++++++++++---- 4 files changed, 214 insertions(+), 38 deletions(-) create mode 100644 engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/KxJsonSchemaFormat.kt diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/KxJsonSchemaFormat.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/KxJsonSchemaFormat.kt new file mode 100644 index 0000000..cbfed78 --- /dev/null +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/KxJsonSchemaFormat.kt @@ -0,0 +1,82 @@ +package de.tuda.stg.securecoder.engine.llm + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject + +@OptIn(ExperimentalSerializationApi::class) +class KxJsonSchemaFormat { + fun format(serializer: KSerializer): JsonObject = + schemaForDescriptor(serializer.descriptor, seen = HashSet()) + + private fun schemaForDescriptor(desc: SerialDescriptor, seen: MutableSet): JsonObject { + val key = desc.serialName + if (!seen.add(key)) { + throw IllegalStateException("Recursive type detected: $key") + } + val jsonType = when (desc.kind) { + PrimitiveKind.BOOLEAN -> type("boolean") + PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG -> type("integer") + PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> type("number") + PrimitiveKind.CHAR, PrimitiveKind.STRING -> type("string") + SerialKind.ENUM -> type("string") { + put("enum", buildJsonArray { + for (i in 0 until desc.elementsCount) { + add(JsonPrimitive(desc.getElementName(i))) + } + }) + } + StructureKind.LIST -> type("array") { + put("items", schemaForDescriptor(desc.getElementDescriptor(0), seen)) + } + StructureKind.MAP -> type("object") { + val keyDesc = desc.getElementDescriptor(0) + if (keyDesc.kind != PrimitiveKind.STRING) { + throw IllegalStateException("Map keys must be strings, but was ${keyDesc.serialName}") + } + put("additionalProperties", schemaForDescriptor(desc.getElementDescriptor(1), seen)) + } + StructureKind.CLASS, StructureKind.OBJECT -> type("object") { + put("properties", buildJsonObject { + for (i in 0 until desc.elementsCount) { + val name = desc.getElementName(i) + val childDesc = desc.getElementDescriptor(i) + put(name, schemaForDescriptor(childDesc, seen)) + } + }) + val required = JsonArray(desc.requiredElements().map { name -> JsonPrimitive(name) }) + if (required.isNotEmpty()) put("required", required) + put("additionalProperties", JsonPrimitive(false)) + } + PolymorphicKind.SEALED, PolymorphicKind.OPEN, SerialKind.CONTEXTUAL + -> throw IllegalStateException("Polymorphic types are not supported") + } + seen.remove(key) + if (desc.isNullable) { + throw IllegalStateException("Nullable types are not supported") + } + return jsonType + } + + private fun type(name: String, builderAction: JsonObjectBuilder.() -> Unit = {}): JsonObject = + buildJsonObject { + put("type", JsonPrimitive(name)) + builderAction() + } + + private fun SerialDescriptor.requiredElements(): List = (0 until elementsCount) + .filter { !isElementOptional(it) } + .map { getElementName(it) } + + private fun JsonArray.isNotEmpty(): Boolean = this.size > 0 +} diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/LlmClient.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/LlmClient.kt index fb6633b..bed5da9 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/LlmClient.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/LlmClient.kt @@ -1,13 +1,27 @@ package de.tuda.stg.securecoder.engine.llm +import kotlinx.serialization.KSerializer +import kotlinx.serialization.serializer + interface LlmClient : AutoCloseable { suspend fun chat( messages: List, params: GenerationParams = GenerationParams(), ): String + suspend fun chatStructured( + messages: List, + serializer: KSerializer, + params: GenerationParams = GenerationParams(), + ): T + data class GenerationParams( val temperature: Double? = null, val maxTokens: Int? = null ) } + +suspend inline fun LlmClient.chatStructured( + messages: List, + params: LlmClient.GenerationParams = LlmClient.GenerationParams(), +): T = this.chatStructured(messages, serializer(), params) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt index ca94d65..d6ec443 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt @@ -12,6 +12,7 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException +import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive @@ -43,7 +44,8 @@ class OllamaClient( val messages: List, val stream: Boolean = false, val options: JsonObject = buildJsonObject {}, - @SerialName("keep_alive") val keepAlive: String? = null + @SerialName("keep_alive") val keepAlive: String? = null, + val format: JsonObject? = null, ) @Serializable @@ -55,11 +57,8 @@ class OllamaClient( @Serializable private data class OllamaError(val error: String) - override suspend fun chat( - messages: List, - params: GenerationParams - ): String { - val mapped = messages.map { + private fun mapMessages(messages: List): List = + messages.map { val role = when (it.role) { Role.System -> "system" Role.User -> "user" @@ -68,37 +67,72 @@ class OllamaClient( OllamaMsg(role, it.content) } - val options = buildJsonObject { - params.temperature?.let { put("temperature", JsonPrimitive(it)) } - params.maxTokens?.let { put("num_predict", JsonPrimitive(it)) } - } + private fun buildOptions(params: GenerationParams) = buildJsonObject { + params.temperature?.let { put("temperature", JsonPrimitive(it)) } + params.maxTokens?.let { put("num_predict", JsonPrimitive(it)) } + } - val req = OllamaChatRequest( - model = model, - messages = mapped, - options = options, - keepAlive = keepAlive - ) - logger.debug("Sending llm request: {}", req) + private suspend fun performRequest(req: OllamaChatRequest): OllamaChatResponse { + logger.debug("Sending LLM request: {}", req) val resp = http.post(endpoint) { contentType(ContentType.Application.Json) accept(ContentType.Application.Json) setBody(req) } val body = resp.bodyAsText() - logger.debug("Got llm response: {}", body) + logger.debug("Got LLM response: {}", body) if (!resp.status.isSuccess()) { val errorMessage = try { json.decodeFromString(body).error } catch (_: SerializationException) { body.ifBlank { "" } } - throw RuntimeException("Failed to call Ollama got ${resp.status}: ${errorMessage}") + throw RuntimeException("Failed to call Ollama got ${resp.status}: $errorMessage") } - val respObj = json.decodeFromString(body) + return json.decodeFromString(body) + } + + override suspend fun chat( + messages: List, + params: GenerationParams + ): String { + val mapped = mapMessages(messages) + val options = buildOptions(params) + val req = OllamaChatRequest( + model = model, + messages = mapped, + options = options, + keepAlive = keepAlive + ) + val respObj = performRequest(req) return respObj.message.content } + override suspend fun chatStructured( + messages: List, + serializer: KSerializer, + params: GenerationParams + ): T { + val mapped = mapMessages(messages) + val options = buildOptions(params) + + val schema = KxJsonSchemaFormat().format(serializer) + val req = OllamaChatRequest( + model = model, + messages = mapped, + options = options, + keepAlive = keepAlive, + format = schema + ) + val respObj = performRequest(req) + val content = respObj.message.content + return try { + json.decodeFromString(serializer, content) + } catch (e: Exception) { + throw RuntimeException("Failed to decode Ollama structured content. Content: $content", e) + } + } + override fun close() = http.close() } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt index cfa05b5..4149a82 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OpenRouterClient.kt @@ -15,12 +15,14 @@ import io.ktor.http.HttpHeaders import io.ktor.http.contentType import io.ktor.http.isSuccess import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.SerializationException import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import org.slf4j.LoggerFactory class OpenRouterClient ( @@ -50,7 +52,8 @@ class OpenRouterClient ( val temperature: Double? = null, @SerialName("max_tokens") val maxTokens: Int? = null, val stream: Boolean = false, - val metadata: JsonObject = buildJsonObject {} + val metadata: JsonObject = buildJsonObject {}, + @SerialName("response_format") val responseFormat: JsonObject? = null, ) @Serializable @@ -59,11 +62,8 @@ class OpenRouterClient ( @Serializable private data class OpenRouterChatResponse(val choices: List) - override suspend fun chat( - messages: List, - params: LlmClient.GenerationParams - ): String { - val mapped = messages.map { + private fun mapMessages(messages: List): List = + messages.map { val role = when (it.role) { Role.System -> "system" Role.User -> "user" @@ -72,14 +72,10 @@ class OpenRouterClient ( OpenRouterMessage(role, it.content) } - val req = OpenRouterChatRequest( - model = model, - messages = mapped, - temperature = params.temperature, - maxTokens = params.maxTokens - ) - - logger.debug("Sending llm request: {}", req) + private suspend fun performRequest( + req: OpenRouterChatRequest, + ): OpenRouterChatResponse { + logger.debug("Sending LLM request: {}", req) val resp: HttpResponse = http.post(endpoint) { contentType(ContentType.Application.Json) accept(ContentType.Application.Json) @@ -89,20 +85,70 @@ class OpenRouterClient ( } val body = resp.bodyAsText() + logger.debug("Got LLM response: {}", body) if (!resp.status.isSuccess()) { - error("OpenRouter Error ${resp.status.value}: $body") + val errorMessage = body.ifBlank { "" } + throw RuntimeException("OpenRouter Error ${resp.status.value}: $errorMessage") } - logger.debug("Got llm response: {}", body) - val obj = try { - json.decodeFromString(body) + return try { + json.decodeFromString(body) } catch (e: SerializationException) { val formattedBody = body.ifBlank { "" } throw RuntimeException("Failed to parse OpenRouter response body. Raw body: $formattedBody", e) } + } + + override suspend fun chat( + messages: List, + params: LlmClient.GenerationParams + ): String { + val mapped = mapMessages(messages) + + val req = OpenRouterChatRequest( + model = model, + messages = mapped, + temperature = params.temperature, + maxTokens = params.maxTokens + ) + val obj = performRequest(req) val content = obj.choices.firstOrNull()?.message?.content ?: error("OpenRouter did not return any response choices ") return content } + override suspend fun chatStructured( + messages: List, + serializer: KSerializer, + params: LlmClient.GenerationParams + ): T { + val mapped = mapMessages(messages) + + val schema = KxJsonSchemaFormat().format(serializer) + val responseFormat = buildJsonObject { + put("type", "json_schema") + put("json_schema", buildJsonObject { + put("name", serializer.descriptor.serialName.ifBlank { "securecoder_schema" }) + put("strict", true) + put("schema", schema) + }) + } + + val req = OpenRouterChatRequest( + model = model, + messages = mapped, + temperature = params.temperature, + maxTokens = params.maxTokens, + responseFormat = responseFormat + ) + val obj = performRequest(req) + val content = obj.choices.firstOrNull()?.message?.content + ?: error("OpenRouter did not return any response choices ") + return try { + json.decodeFromString(serializer, content) + } catch (e: Exception) { + throw RuntimeException("Failed to decode OpenRouter structured content into ${'$'}{serializer.descriptor.serialName}. Content: ${'$'}content", e) + } + } + override fun close() = http.close() } \ No newline at end of file From 86bbd19e7d0df5d1bd5d6a3cda9f04133753e8c2 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sat, 3 Jan 2026 19:46:10 +0100 Subject: [PATCH 38/43] Add LLMDescription annotation --- .../securecoder/engine/llm/KxJsonSchemaFormat.kt | 16 ++++++++++++++-- .../stg/securecoder/engine/llm/LLMDescription.kt | 10 ++++++++++ .../stg/securecoder/engine/llm/OllamaClient.kt | 14 ++++---------- 3 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/LLMDescription.kt diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/KxJsonSchemaFormat.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/KxJsonSchemaFormat.kt index cbfed78..b71f180 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/KxJsonSchemaFormat.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/KxJsonSchemaFormat.kt @@ -51,7 +51,9 @@ class KxJsonSchemaFormat { for (i in 0 until desc.elementsCount) { val name = desc.getElementName(i) val childDesc = desc.getElementDescriptor(i) - put(name, schemaForDescriptor(childDesc, seen)) + val childSchema = schemaForDescriptor(childDesc, seen) + val propDesc = getDescription(desc.getElementAnnotations(i)) + put(name, if (propDesc != null) addDescription(childSchema, propDesc) else childSchema) } }) val required = JsonArray(desc.requiredElements().map { name -> JsonPrimitive(name) }) @@ -65,7 +67,8 @@ class KxJsonSchemaFormat { if (desc.isNullable) { throw IllegalStateException("Nullable types are not supported") } - return jsonType + val selfDesc = getDescription(desc.annotations) + return if (selfDesc != null) addDescription(jsonType, selfDesc) else jsonType } private fun type(name: String, builderAction: JsonObjectBuilder.() -> Unit = {}): JsonObject = @@ -79,4 +82,13 @@ class KxJsonSchemaFormat { .map { getElementName(it) } private fun JsonArray.isNotEmpty(): Boolean = this.size > 0 + + private fun getDescription(annotations: List): String? = + annotations.filterIsInstance().firstOrNull()?.text + + private fun addDescription(obj: JsonObject, text: String): JsonObject = + buildJsonObject { + obj.forEach { (k, v) -> put(k, v) } + put("description", JsonPrimitive(text)) + } } diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/LLMDescription.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/LLMDescription.kt new file mode 100644 index 0000000..c52dbed --- /dev/null +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/LLMDescription.kt @@ -0,0 +1,10 @@ +package de.tuda.stg.securecoder.engine.llm + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerialInfo + +@OptIn(ExperimentalSerializationApi::class) +@SerialInfo +@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class LLMDescription(val text: String) diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt index d6ec443..f81b694 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/llm/OllamaClient.kt @@ -96,13 +96,10 @@ class OllamaClient( messages: List, params: GenerationParams ): String { - val mapped = mapMessages(messages) - val options = buildOptions(params) - val req = OllamaChatRequest( model = model, - messages = mapped, - options = options, + messages = mapMessages(messages), + options = buildOptions(params), keepAlive = keepAlive ) val respObj = performRequest(req) @@ -114,14 +111,11 @@ class OllamaClient( serializer: KSerializer, params: GenerationParams ): T { - val mapped = mapMessages(messages) - val options = buildOptions(params) - val schema = KxJsonSchemaFormat().format(serializer) val req = OllamaChatRequest( model = model, - messages = mapped, - options = options, + messages = mapMessages(messages), + options = buildOptions(params), keepAlive = keepAlive, format = schema ) From d2f179dd269255bb7b6f0ffe5490eec9be85bfb5 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sat, 3 Jan 2026 23:18:05 +0100 Subject: [PATCH 39/43] Add structured output support in logging and replay --- .../engine/llm/LoggingAndReplayClients.kt | 85 ++++++++++++++----- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LoggingAndReplayClients.kt b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LoggingAndReplayClients.kt index 3978cdc..a189d65 100644 --- a/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LoggingAndReplayClients.kt +++ b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/llm/LoggingAndReplayClients.kt @@ -1,5 +1,7 @@ package de.tuda.stg.securecoder.engine.llm +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json import java.security.MessageDigest class LoggingLlmClient( @@ -8,21 +10,43 @@ class LoggingLlmClient( private val _calls = mutableListOf() val calls: List get() = _calls - override suspend fun chat( + private fun appendLog( + requestHash: String, messages: List, - params: LlmClient.GenerationParams - ): String { - val hash = computeHash(messages, params) - val response = delegate.chat(messages, params) + params: LlmClient.GenerationParams, + response: String, + ) { _calls += LoggedCall( - requestHash = hash, + requestHash = requestHash, request = messages.toString(), params = params.toString(), response = response, ) + } + + override suspend fun chat( + messages: List, + params: LlmClient.GenerationParams + ): String { + val hash = computeHash(messages, params) + val response = delegate.chat(messages, params) + appendLog(hash, messages, params, response) return response } + override suspend fun chatStructured( + messages: List, + serializer: KSerializer, + params: LlmClient.GenerationParams + ): T { + val hash = computeHash(messages, params) + val result = delegate.chatStructured(messages, serializer, params) + val json = Json { encodeDefaults = true } + val response = json.encodeToString(serializer, result) + appendLog(hash, messages, params, response) + return result + } + override fun close() = delegate.close() companion object Commons { @@ -42,25 +66,46 @@ class ReplayLlmClient( ) : LlmClient { private var idx = 0 - override suspend fun chat( + private fun mismatchError( + expected: LoggedCall, + calculated: String, + messages: List, + ): IllegalStateException = IllegalStateException( + buildString { + appendLine("ReplayLlmClient: input hash mismatch at call #$idx") + appendLine("expected: ${expected.requestHash}") + appendLine("actual: $calculated") + appendLine("expected (m): ${expected.request}") + appendLine("actual (m): $messages") + } + ) + + private fun verifyNext( messages: List, params: LlmClient.GenerationParams - ): String { + ): LoggedCall { if (idx >= loggedCalls.size) error("ReplayLlmClient: no more calls to replay (idx=$idx, size=${loggedCalls.size})") val calculated = LoggingLlmClient.computeHash(messages, params) val expected = loggedCalls[idx] - if (calculated != expected.requestHash) { - throw IllegalStateException( - buildString { - appendLine("ReplayLlmClient: input hash mismatch at call #$idx") - appendLine("expected: ${expected.requestHash}") - appendLine("actual: $calculated") - appendLine("expected (m): ${expected.request}") - appendLine("actual (m): $messages") - } - ) - } - return loggedCalls[idx++].response + if (calculated != expected.requestHash) throw mismatchError(expected, calculated, messages) + idx++ + return expected + } + + override suspend fun chat( + messages: List, + params: LlmClient.GenerationParams + ): String { + return verifyNext(messages, params).response + } + + override suspend fun chatStructured( + messages: List, + serializer: KSerializer, + params: LlmClient.GenerationParams + ): T { + val expected = verifyNext(messages, params) + return Json.decodeFromString(serializer, expected.response) } override fun close() {} From 899397d0868af2d4b78748cef019a5b397d3cb5a Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sun, 4 Jan 2026 16:40:09 +0100 Subject: [PATCH 40/43] Add StructuredEditFilesLlmWrapper --- .../edit/StructuredEditFilesLlmWrapper.kt | 137 ++++++++++++++++++ .../engine/workflow/WorkflowEngine.kt | 6 +- 2 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/StructuredEditFilesLlmWrapper.kt diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/StructuredEditFilesLlmWrapper.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/StructuredEditFilesLlmWrapper.kt new file mode 100644 index 0000000..2f3c58a --- /dev/null +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/file/edit/StructuredEditFilesLlmWrapper.kt @@ -0,0 +1,137 @@ +package de.tuda.stg.securecoder.engine.file.edit + +import de.tuda.stg.securecoder.engine.file.edit.Changes.SearchedText +import de.tuda.stg.securecoder.engine.llm.ChatMessage +import de.tuda.stg.securecoder.engine.llm.ChatMessage.Role +import de.tuda.stg.securecoder.engine.llm.LlmClient +import de.tuda.stg.securecoder.engine.llm.LLMDescription +import de.tuda.stg.securecoder.engine.llm.chatStructured +import de.tuda.stg.securecoder.filesystem.FileSystem +import de.tuda.stg.securecoder.engine.llm.ChatExchange +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlin.collections.plusAssign + +class StructuredEditFilesLlmWrapper( + private val llmClient: LlmClient +) { + //TODO path => **uri** ; EditFilesLlmWrapper should be separate from the filesystem implementation + private val prompt = """ + Your task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer! + + It's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file. + To create a file: search must be empty and replace must contain the entire file content + Each *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc. + Do not use a part of the line as *search* pattern. You must use full lines. + Include enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error. + Do not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language. + Do not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks. + Do not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters. + Do not use the same value for *search* and *REPLACE* parameters, as this will make no changes. + + If you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**. + """.trimIndent() + + + suspend fun chat( + messages: List, + fileSystem: FileSystem, + params: LlmClient.GenerationParams = LlmClient.GenerationParams(), + onParseError: suspend (parseErrors: List, llm: ChatExchange) -> Unit = { _, _ -> }, + attempts: Int = 3 + ): ChatResult { + val messages = messages.toMutableList() + appendPromptToLastSystem(messages) + repeat(attempts) { + val llmInput = messages.toList() + val structured = llmClient.chatStructured(llmInput, params) + messages += ChatMessage(Role.Assistant, Json.encodeToString(structured)) + when (val result = validateAndConvert(structured, fileSystem)) { + is ParseResult.Ok -> return ChatResult(messages, result.value) + is ParseResult.Err -> { + messages += ChatMessage(Role.User, result.buildMessage()) + onParseError(result.messages, ChatExchange(llmInput, messages.last().content)) + } + } + } + return ChatResult(messages, null) + } + + data class ChatResult(val messages: List, val changes: Changes?) { + fun changesMessage() = messages.last { it.role == Role.Assistant } + } + + sealed interface ParseResult { + data class Ok(val value: Changes) : ParseResult + data class Err(val messages: List) : ParseResult { + fun buildMessage() = buildString { + appendLine("Your previous output could not be applied.") + appendLine("It violated the required format.") + appendLine("Errors:") + messages.forEach { appendLine(it) } + appendLine("Respond again with ONLY edit blocks that strictly follow the rules. Do NOT include prose, markdown, or explanations.") + appendLine("IMPORTANT: Resend the COMPLETE set of edits you intend to apply from your previous message") + } + } + } + + private suspend fun validateAndConvert(structured: StructuredEdits, fileSystem: FileSystem): ParseResult { + val results = mutableListOf() + val allErrors = mutableListOf() + if (structured.edits.isEmpty()) { + allErrors += "No edits provided. Provide at least one edit block." + return ParseResult.Err(allErrors) + } + for (e in structured.edits) { + val file = e.filePath.trim() + val searchPart = e.search + val replacePart = e.replace + if (file.isEmpty()) { + allErrors += "`filePath` should not be empty" + continue + } + if (searchPart == replacePart) { + allErrors += "`search` and `replace` parameters are the same" + continue + } + val replace = Changes.SearchReplace(file, SearchedText(searchPart), replacePart) + val content = fileSystem.getFile(file)?.content() + val match = ApplyChanges.match(content, replace.searchedText) + if (match is Matcher.MatchResult.Error) { + allErrors += ApplyChanges.buildErrorMessage(file, searchPart, match) + continue + } + results += replace + } + if (results.isEmpty()) return ParseResult.Err(allErrors) + return ParseResult.Ok(Changes(results)) + } + + private fun appendPromptToLastSystem(messages: MutableList) { + val lastSystemIndex = messages.indexOfLast { it.role == Role.System } + if (lastSystemIndex >= 0) { + val existing = messages[lastSystemIndex] + val combined = "${existing.content}\n\n$prompt\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations." + messages[lastSystemIndex] = ChatMessage(Role.System, combined) + } else { + messages += ChatMessage(Role.System, "$prompt\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.") + } + } + + @Serializable + data class StructuredEdits( + @LLMDescription("List of edit operations to apply") + val edits: List + ) + + @Serializable + data class EditOperation( + @LLMDescription("The full **uri** of the file that will be modified") + val filePath: String, + @LLMDescription("A continuous, yet concise block of lines to search for in the existing source code (*search* pattern). If this section is empty, the lines from `replace` will be added to the end of the file.") + val search: String, + @LLMDescription("The lines to replace the existing code found using `search`. If this section is empty, the lines specified in `search` will be removed.") + val replace: String, + ) +} diff --git a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt index 668108c..cd333ac 100644 --- a/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt +++ b/engine/src/main/kotlin/de/tuda/stg/securecoder/engine/workflow/WorkflowEngine.kt @@ -5,8 +5,8 @@ import de.tuda.stg.securecoder.engine.Engine.Context import de.tuda.stg.securecoder.engine.Engine.EngineResult import de.tuda.stg.securecoder.engine.llm.ChatMessage import de.tuda.stg.securecoder.engine.llm.ChatMessage.Role -import de.tuda.stg.securecoder.engine.file.edit.EditFilesLlmWrapper -import de.tuda.stg.securecoder.engine.llm.FilesInContextPromptBuilder +import de.tuda.stg.securecoder.engine.file.FilesInContextPromptBuilder +import de.tuda.stg.securecoder.engine.file.edit.StructuredEditFilesLlmWrapper import de.tuda.stg.securecoder.engine.llm.LlmClient import de.tuda.stg.securecoder.engine.stream.StreamEvent import de.tuda.stg.securecoder.engine.stream.ProposalId @@ -23,7 +23,7 @@ class WorkflowEngine ( private val parseChangesAttempts: Int = 5, ) : Engine { private val promptEnrichRunner = PromptEnrichRunner(enricher) - private val editFiles = EditFilesLlmWrapper(llmClient) + private val editFiles = StructuredEditFilesLlmWrapper(llmClient) private val guardianExecutor = GuardianExecutor(guardians) override suspend fun run( From ab87add631dbbf4d70d62d617b61aa63b468fe54 Mon Sep 17 00:00:00 2001 From: David Scandurra <31861387+SplotyCode@users.noreply.github.com> Date: Sun, 4 Jan 2026 16:59:18 +0100 Subject: [PATCH 41/43] Add replay test in ci --- engine/build.gradle.kts | 3 + .../engine/workflow/EngineLlmReplayTests.kt | 48 +- engine/src/test/resources/llm_output.json | 964 ++++++++++++++++++ 3 files changed, 991 insertions(+), 24 deletions(-) create mode 100644 engine/src/test/resources/llm_output.json diff --git a/engine/build.gradle.kts b/engine/build.gradle.kts index daf8cf1..0b4efbb 100644 --- a/engine/build.gradle.kts +++ b/engine/build.gradle.kts @@ -9,4 +9,7 @@ dependencies { implementation(libs.ktor.client.java) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.json) + testImplementation(kotlin("test")) + testImplementation(libs.kotlinx.serialization.json) + testImplementation(libs.kotlinx.coroutines.core) } diff --git a/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/workflow/EngineLlmReplayTests.kt b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/workflow/EngineLlmReplayTests.kt index 12965cc..7489106 100644 --- a/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/workflow/EngineLlmReplayTests.kt +++ b/engine/src/test/kotlin/de/tuda/stg/securecoder/engine/workflow/EngineLlmReplayTests.kt @@ -6,18 +6,25 @@ import de.tuda.stg.securecoder.engine.stream.StreamEvent import de.tuda.stg.securecoder.enricher.PromptEnricher import de.tuda.stg.securecoder.filesystem.InMemoryFileSystem import kotlinx.coroutines.runBlocking +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream import kotlin.test.Test import java.nio.file.Files import java.nio.file.Path +import kotlin.test.Ignore +import kotlin.test.assertIs +import kotlin.test.assertTrue class EngineLlmReplayTests { private val json = Json { prettyPrint = true; ignoreUnknownKeys = true; encodeDefaults = true } - private val logsPath: Path = Path.of("build", "llm_logs", "log.json") + private val resourceName = "llm_output.json" @Test + @Ignore fun generator_collects_real_llm_responses() = runBlocking { + val logsPath: Path = Path.of("src", "test", "resources", resourceName) Files.createDirectories(logsPath.parent) val prompts = listOf( @@ -27,11 +34,11 @@ class EngineLlmReplayTests { val models = buildList { val apiKey = System.getenv("API_KEy") ?: "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" - add("OR:llama3.2:latest" to OpenRouterClient(apiKey, "meta-llama/llama-3.2-3b-instruct")) - add("OR:gpt-oss:20b" to OpenRouterClient(apiKey, "openai/gpt-oss-20b")) - //val olBase = System.getenv("OLLAMA_URL") ?: "http://127.0.0.1:11434" - //add("ollama:llama3.2:latest" to OllamaClient("llama3.2:latest", baseUrl = olBase)) - //add("ollama:gpt-oss:20b" to OllamaClient("gpt-oss:20b", baseUrl = olBase)) + //add("OR:llama3.2:latest" to OpenRouterClient(apiKey, "meta-llama/llama-3.2-3b-instruct")) + //add("OR:gpt-oss:20b" to OpenRouterClient(apiKey, "openai/gpt-oss-20b")) + val olBase = System.getenv("OLLAMA_URL") ?: "http://127.0.0.1:11434" + add("ollama:llama3.2:latest" to OllamaClient("llama3.2:latest", baseUrl = olBase)) + add("ollama:gpt-oss:20b" to OllamaClient("gpt-oss:20b", baseUrl = olBase)) } val runs = mutableListOf() @@ -81,12 +88,11 @@ class EngineLlmReplayTests { } @Test - fun replay_test_uses_recorded_responses_and_counts_success() = runBlocking { - if (!Files.exists(logsPath)) { - println("No log file at $logsPath; nothing to replay. Test will be a no-op.") - return@runBlocking - } - val suite = json.decodeFromString(Files.readString(logsPath)) + @OptIn(ExperimentalSerializationApi::class) + fun test_replay() = runBlocking { + val resourceStream = this@EngineLlmReplayTests::class.java.classLoader.getResourceAsStream(resourceName) + ?: throw IllegalStateException("No $resourceName on classpath") + val suite: LoggedSuite = resourceStream.use { json.decodeFromStream(it) } data class Group( val modelName: String, @@ -95,7 +101,7 @@ class EngineLlmReplayTests { var successes: Int = 0, var parseFails: Int = 0 ) - + assertTrue(suite.runs.isNotEmpty()) suite.runs .groupBy { it.modelName to it.promptKind } .map { (key, runs) -> @@ -112,28 +118,22 @@ class EngineLlmReplayTests { guardians = emptyList(), ) group.total++ - var l = 0 val result = engine.run(run.enginePrompt, fs, onEvent = { if (it !is StreamEvent.InvalidLlmOutputWarning) return@run group.parseFails++ - if (l++ >= 2) { - println("=======ERROR=======") - println("=======ERROR=======") - println("=======ERROR=======") - println(it.parseErrors.joinToString("\n")) - println() - println() - println(it.chatExchange.output) - } + //println(it.parseErrors.joinToString("\n")) + //println(it.chatExchange.output) }) if (result is EngineResult.Success) { group.successes++ } + assertIs(result) } group } .forEach { - println("Group ${it.modelName} / ${it.promptKind}: replayed runs: ${it.total}, successes: ${it.successes} (${it.parseFails} parse failures)") + assertTrue(it.parseFails <= (1.5 * suite.runs.size)) + //println("Group ${it.modelName} / ${it.promptKind}: replayed runs: ${it.total}, successes: ${it.successes} (${it.parseFails} parse failures)") } } } diff --git a/engine/src/test/resources/llm_output.json b/engine/src/test/resources/llm_output.json new file mode 100644 index 0000000..551e55f --- /dev/null +++ b/engine/src/test/resources/llm_output.json @@ -0,0 +1,964 @@ +{ + "runs": [ + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 0, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\npublic static void main(String[] args) {\\n\\tSystem.out.println(\\\"Hello\\\");\\n}}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 1, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\\tpublic static void main(String[] args) {\\n\\t\\tSystem.out.println(\\\"Hello\\\");\\n\\t}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 2, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\\tpublic static void main(String[] args) {\\n\\t\\tSystem.out.println(\\\"Hello\\\");\\n\\t}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 3, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 4, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\\tpublic static void main(String[] args) {\\n\\t\\n\\tSystem.out.println(\\\"Hello\\\");\\n\\t}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 5, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\npublic static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 6, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 7, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\\tpublic static void main(String[] args) {\\n\\t\\tSystem.out.println(\\\"Hello\\\");\\n\\t}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 8, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\"},{\"filePath\":\"Main.java\",\"search\":\"\\\\w+\\\\s+main\\\\s*\\\\(\",\"replace\":\"public static void main(String[] args) { println(\\\"Hello\\\"); }\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 9, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\"},{\"filePath\":\"Main.java\",\"search\":\"\\\\s*public\\\\s*class\\\\s+\",\"replace\":\"public class Hello {\\n\"},{\"filePath\":\"Main.java\",\"search\":\"\\\\s*Main\\\\s*\\\\{\",\"replace\":\"Hello {\\n\"},{\"filePath\":\"Main.java\",\"search\":\"\\\\s*System\\\\.out\\\\.println\\\\(\\\\)\",\"replace\":\"System.out.println(\\\"Hello\\\\);\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 10, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\\t\\n\\tSystem.out.println(\\\"Hello\\\");\\n}\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 11, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\\tpublic static void main(String[] args) {\\n\\t\\tSystem.out.println(\\\"Hello\\\");\\n\\t}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 12, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\npublic static void main(String[] args) {\\nSystem.out.println(\\\"Hello\\\");\\n}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 13, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\npublic static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n}}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "create_project", + "runIndex": 14, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n\\tpublic static void main(String[] args) {\\n\\t\\tSystem.out.println(\\\"Hello\\\");\\n\\t}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 0, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\\n\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\"\",\"replace\":\"import java.util.Scanner;\\npublic class Main {\\n public static void main(String[] args) {\\n String name = \\\"John\\\";\\n System.out.println(Utils.greet(name));\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 1, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"import java.util.function.Function;\\n\\npublic class Utils {\\n public static Function greet(String name) {\\n return () -> \\\"Hello, \\\" + name + \\\"!\\\";\\\\n }\\n}\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 2, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n\\n}\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\"System\\\\.out\\\\.println\\\\(\\\"Hello\\\\\\\")\",\"replace\":\"System.out.println(\\\"Hello, \"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 3, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\npublic static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n}\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"public\\\\s+static\\\\s+void\\\\s+main\\\\(String\\\\s+\\\\[args\\\\]\\\\s+)\\\\s*\\\\{\\\\s*System\\\\.out\\\\.println\\\\(\\\"Hello\\\"\\\\);\\\\s*}\\\\s*;\",\"replace\":\"public class Main {\\npublic static void main(String[] args) {\\n System.out.println(Utils.greet(args[0]));\\n}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 4, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n\\t\\n\\tpublic static String greet(String name) {\\n\\t\\treturn \\\"Hello, \\\\\\\" + name + \\\"!\\\\\\\"; \\n\\t}\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"\",\"replace\":\"\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 5, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name;\\n }\\n}\\n\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\"System\\\\.out\\\\..*print\\\\(.*?\\\\);\\\\s*\\\\{\",\"replace\":\"System.out.println(greet(\\\"Main\\\"));\\\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 6, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 7, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\"\",\"replace\":\"import org.example.Utils;\\n\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\"System\\\\.out\\\\.println\\\\(\\\"Hello\\\\\\\")\",\"replace\":\"Utils\\\\.greet(\\\"World\\\")\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 8, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"import java.util.function Consumable;\\n\\npublic class Utils {\\n public static void greet(String name) {\\n System.out.println(\\\"Hello, \\\\\\\"+name\\\\'\\\");\\n }\\n}\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\"\",\"replace\":\"import java.util.function Consumable;\\n\\npublic class Main {\\n public static void main(String[] args) {\\n Utils.greet(\\\"World\\\");\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 9, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\npublic static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n}\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"\\\\s*public\\\\s*static\\\\s*v\\\\s*main\\\\s*(\\\\(\\\\s*[\\\\s\\\\S]*\\\\)\\\\s*)\\\\s*\\\\{\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(Utils.greet(args[0]));\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 10, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\npublic static String greet(String name) {\\nreturn \\\"Hello, \\\\\\\" + name + '\\\\\\\"';\\n}\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"System\\\\.out\\\\.println\\\\(\\\"(.*?)\\\\\\\")\",\"replace\":\"System.out.println(Utils.greet($1));\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 11, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Main.java\",\"search\":\"\",\"replace\":\"#import java.util.functionConsumer;\\n\\npublic class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\\n\"},{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"import java.util.functionConsumer;\\n\\npublic class Utils {\\n public static void greet(String name) {\\n System.out.println(\\\"Hello, \\\"+name+\\\"!\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 12, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main {.*}.*public static void main\\\\(String\\\\[.*\\\\]\\\\)\",\"replace\":\"public class Main {\\n\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello, how are you?\\\");\\n Utils.greet(\\\"John\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 13, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Main.java\",\"search\":\"\",\"replace\":\"import com.example.Utils;\\n\\npublic class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\\n\"},{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name;\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:llama3.2:latest", + "promptKind": "edit_project", + "runIndex": 14, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n\\t\\n\\tpublic static void main(String[] args) {\\n\\tSystem.out.println(\\\"Hello\\\");\\n\\t}\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main\",\"replace\":\"import com.example.Utils;\\n\\npublic class Main {\\n\\tpublic static void main(String[] args) {\\n\\t\\tUtils.greet(\\\"World\\\");\\n\\t}\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 0, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 1, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 2, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 3, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 4, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 5, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 6, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 7, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 8, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 9, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 10, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 11, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 12, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 13, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "create_project", + "runIndex": 14, + "enginePrompt": "Create a minimal Hello World project with one file Main.java printing Hello", + "initialFiles": {}, + "calls": [ + { + "requestHash": "4508dfa643243496f170abe59fee1b9679a7ad1ad6d5d4efd62a0429a56b861b", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Create a minimal Hello World project with one file Main.java printing Hello), ChatMessage(role=User, content=You have no files in the context.\nIf you saw files they are only part of the prompt and dont exists yet!\nYou may create new files (keep in mind that searched text needs to be empty in this case!)\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"Main.java\",\"search\":\"\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 0, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\" System.out.println(\\\"Hello\\\");\",\"replace\":\" System.out.println(Utils.greet(\\\"World\\\"));\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 1, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\" System.out.println(\\\"Hello\\\");\",\"replace\":\" System.out.println(Utils.greet(\\\"World\\\"));\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 2, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\\n\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(Utils.greet(\\\"Alice\\\"));\\n }\\n}\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 3, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello \\\" + name;\\n }\\n}\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\\n\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n String name = args.length > 0 ? args[0] : \\\"World\\\";\\n System.out.println(Utils.greet(name));\\n }\\n}\\n\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 4, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(Utils.greet(\\\"World\\\"));\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 5, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name;\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\" System.out.println(\\\"Hello\\\");\",\"replace\":\" System.out.println(Utils.greet(\\\"World\\\"));\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 6, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\"System.out.println(\\\"Hello\\\");\",\"replace\":\"System.out.println(Utils.greet(\\\"World\\\"));\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 7, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\" System.out.println(\\\"Hello\\\");\",\"replace\":\" System.out.println(Utils.greet(\\\"World\\\"));\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 8, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name;\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(Utils.greet(\\\"World\\\"));\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 9, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(Utils.greet(\\\"World\\\"));\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 10, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(Utils.greet(\\\"World\\\"));\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 11, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\" System.out.println(\\\"Hello\\\");\",\"replace\":\" System.out.println(Utils.greet(\\\"World\\\"));\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 12, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello \\\" + name;\\n }\\n}\\n\"},{\"filePath\":\"src/Main.java\",\"search\":\" System.out.println(\\\"Hello\\\");\",\"replace\":\" System.out.println(Utils.greet(\\\"World\\\"));\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 13, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello \\\" + name;\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(Utils.greet(\\\"Alice\\\"));\\n }\\n}\"}]}" + } + ] + }, + { + "modelName": "ollama:gpt-oss:20b", + "promptKind": "edit_project", + "runIndex": 14, + "enginePrompt": "Modify the project to add a Utils.java with a function greet(name) and use it in Main.java", + "initialFiles": { + "src/Main.java": "public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}" + }, + "calls": [ + { + "requestHash": "720d4441caf38ea977c388a727df96d22d275e6426d8b54676cd3ae4c587cd66", + "request": "[ChatMessage(role=System, content=You are a Security Engineering Agent mainly for writing secure code\n\nYour task it is to produce code. The agent will just parse the code you produce. So dont do a extensive review in your final answer!\n\nIt's acceptable to add multiple *search/REPLACE* sections if you need to change multiple parts of the file.\nTo create a file: search must be empty and replace must contain the entire file content\nEach *search* pattern must match the existing source code exactly once, line for line, character for character, including all comments, docstrings, etc.\nDo not use a part of the line as *search* pattern. You must use full lines.\nInclude enough lines to make code inside *search* pattern uniquely identifiable. A *search* pattern that produces multiple matches in the source code will be rejected as an error.\nDo not add backslashes to escape special characters. Write the code exactly as it should appear in the intended programming language.\nDo not use git diff style (+ and - at the beginning of the line) for *search/REPLACE* blocks.\nDo not use line numbers in *search/REPLACE* blocks. Do not enclose the *search/REPLACE* block or any of its components in triple quotes. Use only tags to separate the parameters.\nDo not use the same value for *search* and *REPLACE* parameters, as this will make no changes.\n\nIf you need to edit a file again after making changes, use the latest version of the code that includes all your modifications applied during **current session**.\n\nRespond ONLY with a JSON object that matches the provided schema. Do not include explanations.), ChatMessage(role=User, content=Modify the project to add a Utils.java with a function greet(name) and use it in Main.java), ChatMessage(role=User, content=The following files are in the context:\n<<>>public class Main {\n public static void main(String[] args) {\n System.out.println(\"Hello\");\n }\n}<<>>\n)]", + "params": "GenerationParams(temperature=null, maxTokens=null)", + "response": "{\"edits\":[{\"filePath\":\"src/Utils.java\",\"search\":\"\",\"replace\":\"public class Utils {\\n public static String greet(String name) {\\n return \\\"Hello, \\\" + name + \\\"!\\\";\\n }\\n}\"},{\"filePath\":\"src/Main.java\",\"search\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(\\\"Hello\\\");\\n }\\n}\",\"replace\":\"public class Main {\\n public static void main(String[] args) {\\n System.out.println(Utils.greet(\\\"World\\\"));\\n }\\n}\"}]}" + } + ] + } + ] +} \ No newline at end of file From 2ebf44204b265e62a69b0091254e75718843da12 Mon Sep 17 00:00:00 2001 From: Prizmo <54034132+prizmo-prime@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:53:37 +0100 Subject: [PATCH 42/43] update gitignore for python --- SecBenchSuite/Benchmarks/CWEval | 1 + SecBenchSuite/Benchmarks/PurpleLlama | 1 + 2 files changed, 2 insertions(+) create mode 160000 SecBenchSuite/Benchmarks/CWEval create mode 160000 SecBenchSuite/Benchmarks/PurpleLlama diff --git a/SecBenchSuite/Benchmarks/CWEval b/SecBenchSuite/Benchmarks/CWEval new file mode 160000 index 0000000..46ff9de --- /dev/null +++ b/SecBenchSuite/Benchmarks/CWEval @@ -0,0 +1 @@ +Subproject commit 46ff9dea1b987c0ffbc5b29edf4760fba7a07f57 diff --git a/SecBenchSuite/Benchmarks/PurpleLlama b/SecBenchSuite/Benchmarks/PurpleLlama new file mode 160000 index 0000000..edbcbca --- /dev/null +++ b/SecBenchSuite/Benchmarks/PurpleLlama @@ -0,0 +1 @@ +Subproject commit edbcbcae62f36385b11ffe4f84b9b5dcc707c730 From 2f42af758f978cd1157b977c68e87a259789b17e Mon Sep 17 00:00:00 2001 From: Prizmo <54034132+prizmo-prime@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:04:34 +0100 Subject: [PATCH 43/43] Add Python entries to gitignore and update CWEval submodule --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index ee2fc73..a11dcf0 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,14 @@ bin/ /nbdist/ /.nb-gradle/ +### Python ### +**/__pycache__/ +**/*.pyc +**/.venv/ +**/.venvs/ + + + ### VS Code ### .vscode/