diff --git a/gradle.properties b/gradle.properties index 6cec32a9..a638fd8f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Gradle Releases -> https://github.com/gradle/gradle/releases libraryGroup = com.simiacryptus.skyenet -libraryVersion = 1.0.89 +libraryVersion = 1.0.90 gradleVersion = 7.6.1 kotlin.daemon.jvmargs=-Xmx2g diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CmdPatchApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CmdPatchApp.kt index aabda18f..f892f7ae 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CmdPatchApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CmdPatchApp.kt @@ -17,7 +17,7 @@ class CmdPatchApp( session: Session, settings: Settings, api: OpenAIClient, - val virtualFiles: Array?, + val files: Array?, model: OpenAITextModel ) : PatchApp(root.toFile(), session, settings, api, model) { companion object { @@ -59,7 +59,7 @@ class CmdPatchApp( return codeFiles } - override fun codeFiles() = getFiles(virtualFiles) + override fun codeFiles() = getFiles(files) .filter { it.toFile().length() < 1024 * 1024 / 2 } // Limit to 0.5MB .map { root.toPath().relativize(it) ?: it }.toSet() diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CommandPatchApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CommandPatchApp.kt new file mode 100644 index 00000000..7920a4ed --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/CommandPatchApp.kt @@ -0,0 +1,82 @@ +package com.simiacryptus.skyenet.apps.general + +import com.simiacryptus.diff.FileValidationUtils +import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.jopenai.models.OpenAITextModel +import com.simiacryptus.skyenet.core.platform.Session +import com.simiacryptus.skyenet.webui.session.SessionTask +import java.io.File +import java.nio.file.Path + +class CommandPatchApp( + root: File, + session: Session, + settings: Settings, + api: OpenAIClient, + model: OpenAITextModel, + private val files: Array?, + val command: String, +) : PatchApp(root, session, settings, api, model) { + override fun codeFiles() = getFiles(files) + .filter { it.toFile().length() < 1024 * 1024 / 2 } // Limit to 0.5MB + .map { root.toPath().relativize(it) ?: it }.toSet() + + override fun codeSummary(paths: List): String = paths + .filter { it.toFile().exists() } + .joinToString("\n\n") { path -> + """ + |# ${settings.workingDirectory?.toPath()?.relativize(path)} + |$tripleTilde${path.toString().split('.').lastOrNull()} + |${path.toFile().readText(Charsets.UTF_8)} + |$tripleTilde + """.trimMargin() + } + + override fun output(task: SessionTask) = OutputResult( + exitCode = 1, + output = command + ) + + override fun projectSummary(): String { + val codeFiles = codeFiles() + return codeFiles + .asSequence() + .filter { settings.workingDirectory?.toPath()?.resolve(it)?.toFile()?.exists() == true } + .distinct().sorted() + .joinToString("\n") { path -> + "* ${path} - ${ + settings.workingDirectory?.toPath()?.resolve(path)?.toFile()?.length() ?: "?" + } bytes".trim() + } + } + + override fun searchFiles(searchStrings: List): Set { + return searchStrings.flatMap { searchString -> + FileValidationUtils.filteredWalk(settings.workingDirectory!!) { !FileValidationUtils.isGitignore(it.toPath()) } + .filter { FileValidationUtils.isLLMIncludable(it) } + .filter { it.readText().contains(searchString, ignoreCase = true) } + .map { it.toPath() } + .toList() + }.toSet() + } + + companion object { + fun getFiles( + files: Array? + ): MutableSet { + val codeFiles = mutableSetOf() // Set to avoid duplicates + files?.forEach { file -> + if (file.isDirectory) { + if (file.name.startsWith(".")) return@forEach + if (FileValidationUtils.isGitignore(file.toPath())) return@forEach + if (file.name.endsWith(".png")) return@forEach + if (file.length() > 1024 * 256) return@forEach + codeFiles.addAll(getFiles(file.listFiles())) + } else { + codeFiles.add((file.toPath())) + } + } + return codeFiles + } + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt index 4e44da9a..0a32231e 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/general/PatchApp.kt @@ -354,4 +354,7 @@ abstract class PatchApp( content.append("
${MarkdownUtil.renderMarkdown(markdown!!)}
") } -} \ No newline at end of file +} + + + diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt new file mode 100644 index 00000000..5733083b --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractAnalysisTask.kt @@ -0,0 +1,83 @@ +package com.simiacryptus.skyenet.apps.plan + +import com.simiacryptus.skyenet.TabbedDisplay +import com.simiacryptus.skyenet.core.actors.ParsedResponse +import com.simiacryptus.skyenet.core.actors.SimpleActor +import com.simiacryptus.skyenet.webui.session.SessionTask +import com.simiacryptus.skyenet.apps.general.PatchApp +import com.simiacryptus.jopenai.OpenAIClient +import com.simiacryptus.skyenet.apps.general.CommandPatchApp +import org.slf4j.LoggerFactory +import java.io.File + +abstract class AbstractAnalysisTask( + settings: Settings, + planTask: PlanTask +) : AbstractTask(settings, planTask) { + + abstract val actorName: String + abstract val actorPrompt: String + + protected val analysisActor by lazy { + SimpleActor( + name = actorName, + prompt = actorPrompt, + model = settings.model, + temperature = settings.temperature, + ) + } + + override fun run( + agent: PlanCoordinator, + taskId: String, + userMessage: String, + plan: ParsedResponse, + genState: PlanCoordinator.GenState, + task: SessionTask, + taskTabs: TabbedDisplay + ) { + val analysisResult = analysisActor.answer( + listOf( + userMessage, + plan.text, + getPriorCode(genState), + getInputFileCode(), + "${getAnalysisInstruction()}:\n${getInputFileCode()}", + ).filter { it.isNotBlank() }, api = agent.api + ) + genState.taskResult[taskId] = analysisResult + applyChanges(agent, task, analysisResult) + } + + abstract fun getAnalysisInstruction(): String + + private fun applyChanges(agent: PlanCoordinator, task: SessionTask, analysisResult: String) { + val outputResult = CommandPatchApp( + root = agent.root.toFile(), + session = agent.session, + settings = PatchApp.Settings( + executable = File("dummy"), + workingDirectory = agent.root.toFile(), + exitCodeOption = "nonzero", + additionalInstructions = "", + autoFix = agent.settings.autoFix + ), + api = agent.api as OpenAIClient, + model = agent.settings.model, + files = agent.files, + command = analysisResult + ).run( + ui = agent.ui, + task = task + ) + if (outputResult.exitCode == 0) { + task.add("${actorName} completed and suggestions have been applied successfully.") + } else { + task.add("${actorName} completed, but failed to apply suggestions. Exit code: ${outputResult.exitCode}") + } + } + + companion object { + private val log = LoggerFactory.getLogger(AbstractAnalysisTask::class.java) + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt index 61f1bd9a..879f8ee7 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/AbstractTask.kt @@ -11,12 +11,12 @@ import java.nio.file.Path abstract class AbstractTask( val settings: Settings, - val task: PlanCoordinator.Task + val planTask: PlanTask ) { - val outputFiles: List? = task.output_files - val inputFiles: List? = task.input_files - val taskDependencies: List? = task.task_dependencies - val description: String? = task.description + val outputFiles: List? = planTask.output_files + val inputFiles: List? = planTask.input_files + val taskDependencies: List? = planTask.task_dependencies + val description: String? = planTask.description var state: TaskState? = TaskState.Pending val codeFiles = mutableMapOf() diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt new file mode 100644 index 00000000..b2c70f4d --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeOptimizationTask.kt @@ -0,0 +1,42 @@ +package com.simiacryptus.skyenet.apps.plan + +import org.slf4j.LoggerFactory + +class CodeOptimizationTask( + settings: Settings, + planTask: PlanTask +) : AbstractAnalysisTask(settings, planTask) { + override val actorName = "CodeOptimization" + override val actorPrompt = """ + Analyze the provided code and suggest optimizations to improve code quality. Focus exclusively on: + 1. Code structure and organization + 2. Readability improvements + 3. Maintainability enhancements + 4. Proper use of language-specific features and best practices + 5. Design pattern applications + | + |Provide detailed explanations for each suggested optimization, including: + |- The reason for the optimization + |- The expected benefits + |- Any potential trade-offs or considerations + | + Format the response as a markdown document with appropriate headings and code snippets. + Use diff format to show the proposed changes clearly. + """.trimIndent() + + override fun promptSegment(): String { + return """ +CodeOptimization - Analyze and optimize existing code for better readability, maintainability, and adherence to best practices + ** Specify the files to be optimized + ** Optionally provide specific areas of focus for the optimization (e.g., code structure, readability, design patterns) + """.trimMargin() + } + + override fun getAnalysisInstruction(): String { + return "Optimize the following code for better readability and maintainability" + } + + companion object { + private val log = LoggerFactory.getLogger(CodeOptimizationTask::class.java) + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt new file mode 100644 index 00000000..41b85706 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CodeReviewTask.kt @@ -0,0 +1,37 @@ +package com.simiacryptus.skyenet.apps.plan + +import org.slf4j.LoggerFactory + +class CodeReviewTask( + settings: Settings, + planTask: PlanTask +) : AbstractAnalysisTask(settings, planTask) { + override val actorName: String = "CodeReview" + override val actorPrompt: String = """ + Perform a comprehensive code review for the provided code files. Analyze the code for: + 1. Code quality and readability + 2. Potential bugs or errors + |3. Performance issues + |4. Security vulnerabilities + |5. Adherence to best practices and coding standards + |6. Suggestions for improvements or optimizations + | + Provide a detailed review with specific examples and recommendations for each issue found. + Format the response as a markdown document with appropriate headings and code snippets. + """.trimIndent() + + override fun getAnalysisInstruction(): String = "Review the following code" + + override fun promptSegment(): String { + return """ + |CodeReview - Perform an automated code review and provide suggestions for improvements + | ** Specify the files to be reviewed + | ** Optionally provide specific areas of focus for the review + """.trimMargin() + } + + + companion object { + private val log = LoggerFactory.getLogger(CodeReviewTask::class.java) + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt index b12a8d00..e4c55de8 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/CommandAutoFixTask.kt @@ -14,8 +14,8 @@ import java.util.concurrent.Semaphore class CommandAutoFixTask( settings: Settings, - task: PlanCoordinator.Task -) : AbstractTask(settings, task) { + planTask: PlanTask +) : AbstractTask(settings, planTask) { override fun promptSegment(): String { return """ |CommandAutoFix - Run a command and automatically fix any issues that arise @@ -46,7 +46,7 @@ class CommandAutoFixTask( } else { Retryable(agent.ui, task = task) { val task = agent.ui.newTask(false).apply { it.append(placeholder) } - val alias = this.task.command?.first() + val alias = this.planTask.command?.first() val commandAutoFixCommands = agent.settings.commandAutoFixCommands val cmds = commandAutoFixCommands.filter { File(it).name.startsWith(alias ?: "") @@ -60,14 +60,14 @@ class CommandAutoFixTask( session = agent.session, settings = PatchApp.Settings( executable = File(executable), - arguments = this.task.command?.drop(1)?.joinToString(" ") ?: "", + arguments = this.planTask.command?.drop(1)?.joinToString(" ") ?: "", workingDirectory = agent.root.toFile(), exitCodeOption = "nonzero", additionalInstructions = "", autoFix = agent.settings.autoFix ), api = agent.api as OpenAIClient, - virtualFiles = agent.virtualFiles, + files = agent.files, model = agent.settings.model, ).run( ui = agent.ui, diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt index fdef326d..b23f8ad3 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/DocumentationTask.kt @@ -11,8 +11,8 @@ import java.util.concurrent.Semaphore class DocumentationTask( settings: Settings, - task: PlanCoordinator.Task -) : AbstractTask(settings, task) { + planTask: PlanTask +) : AbstractTask(settings, planTask) { override fun promptSegment(): String { return """ |Documentation - Generate documentation diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/EditFileTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/EditFileTask.kt deleted file mode 100644 index 42284e01..00000000 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/EditFileTask.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.simiacryptus.skyenet.apps.plan - -import com.simiacryptus.diff.addApplyFileDiffLinks -import com.simiacryptus.skyenet.Retryable -import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.core.actors.ParsedResponse -import com.simiacryptus.skyenet.core.actors.SimpleActor -import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil -import org.slf4j.LoggerFactory -import java.util.concurrent.Semaphore - -class EditFileTask( - settings: Settings, - task: PlanCoordinator.Task -) : AbstractTask(settings, task) { - val filePatcherActor by lazy { - SimpleActor( - name = "FilePatcher", - prompt = """ - |Generate a patch for an existing file to modify its functionality or fix issues based on the given requirements and context. - |Ensure the modifications are efficient, maintain readability, and adhere to coding standards. - |Carefully review the existing code and project structure to ensure the changes are consistent and do not introduce bugs. - |Consider the impact of the modifications on other parts of the codebase. - | - |Provide a summary of the changes made. - | - |Response should use one or more code patches in diff format within ${TRIPLE_TILDE}diff code blocks. - |Each diff should be preceded by a header that identifies the file being modified. - |The diff format should use + for line additions, - for line deletions. - |The diff should include 2 lines of context before and after every change. - | - |Example: - | - |Here are the patches: - | - |### src/utils/exampleUtils.js - |${TRIPLE_TILDE}diff - | // Utility functions for example feature - | const b = 2; - | function exampleFunction() { - |- return b + 1; - |+ return b + 2; - | } - |$TRIPLE_TILDE - | - |### tests/exampleUtils.test.js - |${TRIPLE_TILDE}diff - | // Unit tests for exampleUtils - | const assert = require('assert'); - | const { exampleFunction } = require('../src/utils/exampleUtils'); - | - | describe('exampleFunction', () => { - |- it('should return 3', () => { - |+ it('should return 4', () => { - | assert.equal(exampleFunction(), 3); - | }); - | }); - |$TRIPLE_TILDE - """.trimMargin(), - model = settings.model, - temperature = settings.temperature, - ) - } - - override fun promptSegment(): String { - return """ - |EditFile - Modify existing files - | ** For each file, specify the relative file path and the goal of the modification - | ** List input files/tasks to be examined when designing the modifications - """.trimMargin() - } - - override fun run( - agent: PlanCoordinator, - taskId: String, - userMessage: String, - plan: ParsedResponse, - genState: PlanCoordinator.GenState, - task: SessionTask, - taskTabs: TabbedDisplay - ) { - val semaphore = Semaphore(0) - val onComplete = { semaphore.release() } - val process = { sb: StringBuilder -> - val codeResult = filePatcherActor.answer( - listOf( - userMessage, - plan.text, - getPriorCode(genState), - getInputFileCode(), - this.description ?: "", - ).filter { it.isNotBlank() }, agent.api - ) - genState.taskResult[taskId] = codeResult - if (agent.settings.autoFix) { - val diffLinks = agent.ui.socketManager!!.addApplyFileDiffLinks( - root = agent.root, - response = codeResult, - handle = { newCodeMap -> - newCodeMap.forEach { (path, newCode) -> - task.complete("$path Updated") - } - }, - ui = agent.ui, - api = agent.api, - shouldAutoApply = { true } - ) - taskTabs.selectedTab += 1 - taskTabs.update() - task.complete() - onComplete() - MarkdownUtil.renderMarkdown(diffLinks + "\n\n## Auto-applied changes", ui = agent.ui) - } else { - MarkdownUtil.renderMarkdown( - agent.ui.socketManager!!.addApplyFileDiffLinks( - root = agent.root, - response = codeResult, - handle = { newCodeMap -> - newCodeMap.forEach { (path, newCode) -> - task.complete("$path Updated") - } - }, - ui = agent.ui, - api = agent.api - ) + acceptButtonFooter(agent.ui) { - taskTabs.selectedTab += 1 - taskTabs.update() - task.complete() - onComplete() - }, ui = agent.ui - ) - } - } - Retryable(agent.ui, task = task, process = process) - try { - semaphore.acquire() - } catch (e: Throwable) { - PlanCoordinator.log.warn("Error", e) - } - } - - companion object { - private val log = LoggerFactory.getLogger(EditFileTask::class.java) - } -} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt index 0df880db..0eca51c7 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/FileModificationTask.kt @@ -12,8 +12,8 @@ import java.util.concurrent.Semaphore class FileModificationTask( settings: Settings, - task: PlanCoordinator.Task -) : AbstractTask(settings, task) { + planTask: PlanTask +) : AbstractTask(settings, planTask) { val fileModificationActor by lazy { SimpleActor( name = "FileModification", diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt index 009cc3da..1556ebb5 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/InquiryTask.kt @@ -15,8 +15,8 @@ import java.util.concurrent.atomic.AtomicReference class InquiryTask( settings: Settings, - task: PlanCoordinator.Task -) : AbstractTask(settings, task) { + planTask: PlanTask +) : AbstractTask(settings, planTask) { val inquiryActor by lazy { SimpleActor( name = "Inquiry", diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/NewFileTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/NewFileTask.kt deleted file mode 100644 index f429fa60..00000000 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/NewFileTask.kt +++ /dev/null @@ -1,138 +0,0 @@ -package com.simiacryptus.skyenet.apps.plan - -import com.simiacryptus.diff.addApplyFileDiffLinks -import com.simiacryptus.skyenet.Retryable -import com.simiacryptus.skyenet.TabbedDisplay -import com.simiacryptus.skyenet.core.actors.ParsedResponse -import com.simiacryptus.skyenet.core.actors.SimpleActor -import com.simiacryptus.skyenet.webui.session.SessionTask -import com.simiacryptus.skyenet.webui.util.MarkdownUtil -import org.slf4j.LoggerFactory -import java.util.concurrent.Semaphore - -class NewFileTask( - settings: Settings, - task: PlanCoordinator.Task -) : AbstractTask(settings, task) { - val newFileCreatorActor by lazy { - SimpleActor( - name = "NewFileCreator", - prompt = """ - |Generate the necessary code for new files based on the given requirements and context. - |For each file: - |Provide a clear relative file path based on the content and purpose of the file. - |Ensure the code is well-structured, follows best practices, and meets the specified functionality. - |Carefully consider how the new file fits into the existing project structure and architecture. - |Avoid creating files that duplicate functionality or introduce inconsistencies. - | - |The response format should be as follows: - |- Use triple backticks to create code blocks for each file. - |- Each code block should be preceded by a header specifying the file path. - |- The file path should be a relative path from the project root. - |- Separate code blocks with a single blank line. - |- Specify the language for syntax highlighting after the opening triple backticks. - | - |Example: - | - |Here are the new files: - | - |### src/utils/exampleUtils.js - |${TRIPLE_TILDE}js - |// Utility functions for example feature - |const b = 2; - |function exampleFunction() { - | return b + 1; - |} - | - |$TRIPLE_TILDE - | - |### tests/exampleUtils.test.js - |${TRIPLE_TILDE}js - |// Unit tests for exampleUtils - |const assert = require('assert'); - |const { exampleFunction } = require('../src/utils/exampleUtils'); - | - |describe('exampleFunction', () => { - | it('should return 3', () => { - | assert.equal(exampleFunction(), 3); - | }); - |}); - $TRIPLE_TILDE - """.trimMargin(), - model = settings.model, - temperature = settings.temperature, - ) - } - - override fun promptSegment(): String { - return """ - |NewFile - Create one or more new files, carefully considering how they fit into the existing project structure - | ** For each file, specify the relative file path and the purpose of the file - | ** List input files/tasks to be examined when authoring the new files - """.trimMargin() - } - - override fun run( - agent: PlanCoordinator, - taskId: String, - userMessage: String, - plan: ParsedResponse, - genState: PlanCoordinator.GenState, - task: SessionTask, - taskTabs: TabbedDisplay - ) { - val semaphore = Semaphore(0) - val onComplete = { semaphore.release() } - val process = { sb: StringBuilder -> - val codeResult = newFileCreatorActor.answer( - listOf( - userMessage, - plan.text, - getPriorCode(genState), - getInputFileCode(), - this.description ?: "", - ).filter { it.isNotBlank() }, agent.api - ) - genState.taskResult[taskId] = codeResult - if (agent.settings.autoFix) { - val diffLinks = agent.ui.socketManager!!.addApplyFileDiffLinks( - agent.root, - codeResult, - api = agent.api, - ui = agent.ui, - shouldAutoApply = { true }) - taskTabs.selectedTab += 1 - taskTabs.update() - onComplete() - MarkdownUtil.renderMarkdown(diffLinks + "\n\n## Auto-applied changes", ui = agent.ui) - } else { - MarkdownUtil.renderMarkdown( - agent.ui.socketManager!!.addApplyFileDiffLinks( - agent.root, - codeResult, - api = agent.api, - ui = agent.ui - ), - ui = agent.ui - ) + acceptButtonFooter(agent.ui) { - taskTabs.selectedTab += 1 - taskTabs.update() - onComplete() - } - } - } - Retryable( - agent.ui, task = task, - process = process - ) - try { - semaphore.acquire() - } catch (e: Throwable) { - PlanCoordinator.log.warn("Error", e) - } - } - - companion object { - private val log = LoggerFactory.getLogger(NewFileTask::class.java) - } -} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt new file mode 100644 index 00000000..ab640679 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PerformanceAnalysisTask.kt @@ -0,0 +1,43 @@ +package com.simiacryptus.skyenet.apps.plan + +import org.slf4j.LoggerFactory + + +class PerformanceAnalysisTask( + settings: Settings, + planTask: PlanTask +) : AbstractAnalysisTask(settings, planTask) { + override val actorName = "PerformanceAnalysis" + override val actorPrompt = """ +Analyze the provided code for performance issues and bottlenecks. Focus exclusively on: +1. Time complexity of algorithms +2. Memory usage and potential leaks +3. I/O operations and network calls +4. Concurrency and parallelism opportunities +5. Caching and memoization possibilities + +Provide detailed explanations for each identified performance issue, including: + The reason it's a performance concern + The potential impact on system performance +- Quantitative estimates of performance impact where possible + +Format the response as a markdown document with appropriate headings and code snippets. +Do not provide code changes, focus on analysis and recommendations. + """.trimIndent() + + override fun promptSegment(): String { + return """ +PerformanceAnalysis - Analyze code for performance issues and suggest improvements + ** Specify the files to be analyzed + ** Optionally provide specific areas of focus for the analysis (e.g., time complexity, memory usage, I/O operations) + """.trimMargin() + } + + override fun getAnalysisInstruction(): String { + return "Analyze the following code for performance issues and provide a detailed report" + } + + companion object { + private val log = LoggerFactory.getLogger(PerformanceAnalysisTask::class.java) + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt index 3a45bd1b..8275571c 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanCoordinator.kt @@ -4,7 +4,6 @@ package com.simiacryptus.skyenet.apps.plan import com.simiacryptus.diff.FileValidationUtils import com.simiacryptus.jopenai.API import com.simiacryptus.jopenai.ApiModel -import com.simiacryptus.jopenai.describe.Description import com.simiacryptus.jopenai.util.ClientUtil.toContentList import com.simiacryptus.jopenai.util.JsonUtil import com.simiacryptus.skyenet.AgentPatterns @@ -38,29 +37,18 @@ class PlanCoordinator( private val taskBreakdownActor by lazy { settings.planningActor() } data class TaskBreakdownResult( - val tasksByID: Map? = null, + val tasksByID: Map? = null, val finalTaskID: String? = null, ) val pool: ThreadPoolExecutor by lazy { ApplicationServices.clientManager.getPool(session, user) } - data class Task( - val description: String? = null, - val taskType: TaskType? = null, - var task_dependencies: List? = null, - val input_files: List? = null, - val output_files: List? = null, - var state: AbstractTask.TaskState? = null, - @Description("Command and arguments (in list form) for the task") - val command: List? = null, - ) - - val virtualFiles: Array by lazy { + val files: Array by lazy { FileValidationUtils.expandFileList(root.toFile()) } private val codeFiles: Map - get() = virtualFiles + get() = files .filter { it.exists() && it.isFile } .filter { !it.name.startsWith(".") } .associate { file -> getKey(file) to getValue(file) } @@ -78,22 +66,16 @@ class PlanCoordinator( fun startProcess(userMessage: String) { val codeFiles = codeFiles val eventStatus = if (!codeFiles.all { it.key.toFile().isFile } || codeFiles.size > 2) """ - Files: - ${codeFiles.keys.joinToString("\n") { "* $it" }} - """.trimMargin() else { - """ - |${ - virtualFiles.joinToString("\n\n") { - val path = root.relativize(it.toPath()) + | Files: + | ${codeFiles.keys.joinToString("\n") { "* $it" }} + """.trimMargin() else files.joinToString("\n\n") { + val path = root.relativize(it.toPath()) """ |## $path | |${(codeFiles[path] ?: "").let { "$TRIPLE_TILDE\n${it/*.indent(" ")*/}\n$TRIPLE_TILDE" }} - """.trimMargin() - } + """.trimMargin() } - """.trimMargin() - } val task = ui.newTask() val toInput = { it: String -> listOf( @@ -171,7 +153,7 @@ class PlanCoordinator( fun executePlan( task: SessionTask, diagramBuffer: StringBuilder?, - subTasks: Map, + subTasks: Map, diagramTask: SessionTask, genState: GenState, taskIdProcessingQueue: MutableList, @@ -248,7 +230,7 @@ class PlanCoordinator( try { val dependencies = subTask.task_dependencies?.toMutableSet() ?: mutableSetOf() dependencies += getAllDependencies( - subTask = subTask, + subPlanTask = subTask, subTasks = genState.subTasks, visited = mutableSetOf() ) @@ -299,12 +281,12 @@ class PlanCoordinator( } private fun getAllDependencies( - subTask: Task, - subTasks: Map, + subPlanTask: PlanTask, + subTasks: Map, visited: MutableSet ): List { - val dependencies = subTask.task_dependencies?.toMutableList() ?: mutableListOf() - subTask.task_dependencies?.forEach { dep -> + val dependencies = subPlanTask.task_dependencies?.toMutableList() ?: mutableListOf() + subPlanTask.task_dependencies?.forEach { dep -> if (dep in visited) return@forEach val subTask = subTasks[dep] if (subTask != null) { @@ -318,7 +300,7 @@ class PlanCoordinator( companion object { val log = LoggerFactory.getLogger(PlanCoordinator::class.java) - fun executionOrder(tasks: Map): List { + fun executionOrder(tasks: Map): List { val taskIds: MutableList = mutableListOf() val taskMap = tasks.toMutableMap() while (taskMap.isNotEmpty()) { @@ -347,7 +329,7 @@ class PlanCoordinator( .replace("\"", "\\\"") .let { '"' + it + '"' } - fun buildMermaidGraph(subTasks: Map): String { + fun buildMermaidGraph(subTasks: Map): String { val graphBuilder = StringBuilder("graph TD;\n") subTasks.forEach { (taskId, task) -> val sanitizedTaskId = sanitizeForMermaid(taskId) @@ -378,8 +360,8 @@ class PlanCoordinator( } data class GenState( - val subTasks: Map, - val tasksByDescription: MutableMap = subTasks.entries.toTypedArray() + val subTasks: Map, + val tasksByDescription: MutableMap = subTasks.entries.toTypedArray() .associate { it.value.description to it.value }.toMutableMap(), val taskIdProcessingQueue: MutableList = executionOrder(subTasks).toMutableList(), val taskResult: MutableMap = mutableMapOf(), diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanTask.kt new file mode 100644 index 00000000..6f92d87d --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanTask.kt @@ -0,0 +1,16 @@ +package com.simiacryptus.skyenet.apps.plan + +import com.simiacryptus.jopenai.describe.Description + +data class PlanTask( + val description: String? = null, + val taskType: TaskType? = null, + var task_dependencies: List? = null, + val input_files: List? = null, + val output_files: List? = null, + var state: AbstractTask.TaskState? = null, + @Description("Command and arguments (in list form) for the task") + val command: List? = null, + @Description("Working directory for the command execution") + val workingDir: String? = null +) \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt index 47e6b528..a30fc431 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/PlanningTask.kt @@ -1,6 +1,5 @@ package com.simiacryptus.skyenet.apps.plan -import com.simiacryptus.diff.addApplyFileDiffLinks import com.simiacryptus.jopenai.ApiModel import com.simiacryptus.jopenai.util.ClientUtil.toContentList import com.simiacryptus.jopenai.util.JsonUtil @@ -16,8 +15,8 @@ import org.slf4j.LoggerFactory class PlanningTask( settings: Settings, - task: PlanCoordinator.Task -) : AbstractTask(settings, task) { + planTask: PlanTask +) : AbstractTask(settings, planTask) { val taskBreakdownActor by lazy { settings.planningActor() } override fun promptSegment(): String { diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt new file mode 100644 index 00000000..86939b5c --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RefactorTask.kt @@ -0,0 +1,42 @@ +package com.simiacryptus.skyenet.apps.plan + +import org.slf4j.LoggerFactory + +class RefactorTask( + settings: Settings, + planTask: PlanTask +) : AbstractAnalysisTask(settings, planTask) { + override val actorName: String = "Refactor" + override val actorPrompt: String = """ +Analyze the provided code and suggest refactoring to improve code structure, readability, and maintainability. Focus on: +1. Improving code organization +2. Reducing code duplication +3. Enhancing modularity +4. Applying design patterns where appropriate +5. Improving naming conventions +6. Simplifying complex logic + +Provide detailed explanations for each suggested refactoring, including: +- The reason for the refactoring +- The expected benefits +- Any potential trade-offs or considerations + +Format the response as a markdown document with appropriate headings and code snippets. +Use diff format to show the proposed changes clearly. + """.trimIndent() + + override fun getAnalysisInstruction(): String = "Refactor the following code" + + override fun promptSegment(): String { + return """ +RefactorTask - Analyze and refactor existing code to improve structure, readability, and maintainability + ** Specify the files to be refactored + ** Optionally provide specific areas of focus for the refactoring (e.g., modularity, design patterns, naming conventions) + """.trimMargin() + } + + + companion object { + private val log = LoggerFactory.getLogger(RefactorTask::class.java) + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt index 7244715b..8b4f4dd0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/RunShellCommandTask.kt @@ -14,20 +14,20 @@ import kotlin.reflect.KClass class RunShellCommandTask( settings: Settings, - task: PlanCoordinator.Task -) : AbstractTask(settings, task) { + planTask: PlanTask +) : AbstractTask(settings, planTask) { val shellCommandActor by lazy { CodingActor( name = "RunShellCommand", interpreterClass = ProcessInterpreter::class, details = """ - |Execute the following shell command(s) and provide the output. Ensure to handle any errors or exceptions gracefully. + Execute the following shell command(s) and provide the output. Ensure to handle any errors or exceptions gracefully. | - |Note: This task is for running simple and safe commands. Avoid executing commands that can cause harm to the system or compromise security. + Note: This task is for running simple and safe commands. Avoid executing commands that can cause harm to the system or compromise security. """.trimMargin(), symbols = mapOf( "env" to settings.env, - "workingDir" to File(settings.workingDir).absolutePath, + "workingDir" to (planTask.workingDir?.let { File(it).absolutePath } ?: File(settings.workingDir).absolutePath), "language" to settings.language, "command" to settings.command, ), @@ -38,9 +38,10 @@ class RunShellCommandTask( override fun promptSegment(): String { return """ - |RunShellCommand - Execute shell commands and provide the output - | ** Specify the command to be executed, or describe the task to be performed - | ** List input files/tasks to be examined when writing the command + RunShellCommand - Execute shell commands and provide the output + ** Specify the command to be executed, or describe the task to be performed + ** List input files/tasks to be examined when writing the command + ** Optionally specify a working directory for the command execution """.trimMargin() } diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt new file mode 100644 index 00000000..449b7614 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/SecurityAuditTask.kt @@ -0,0 +1,37 @@ +package com.simiacryptus.skyenet.apps.plan + +import org.slf4j.LoggerFactory + +class SecurityAuditTask( + settings: Settings, + planTask: PlanTask +) : AbstractAnalysisTask(settings, planTask) { + override val actorName: String = "SecurityAudit" + override val actorPrompt: String = """ +Perform a comprehensive security audit for the provided code files. Analyze the code for: +1. Potential security vulnerabilities +2. Insecure coding practices +3. Compliance with security standards and best practices +4. Proper handling of sensitive data +5. Authentication and authorization issues +6. Input validation and sanitization + +Provide a detailed audit report with specific examples and recommendations for each issue found. +Format the response as a markdown document with appropriate headings and code snippets. +Use diff format to show the proposed security fixes clearly. + """.trimIndent() + override fun getAnalysisInstruction(): String = "Perform a security audit on the following code" + + override fun promptSegment(): String { + return """ +SecurityAudit - Perform an automated security audit and provide suggestions for improving code security + ** Specify the files to be audited + ** Optionally provide specific areas of focus for the security audit + """.trimMargin() + } + + + companion object { + private val log = LoggerFactory.getLogger(SecurityAuditTask::class.java) + } +} \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/Settings.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/Settings.kt index d034e7cd..a96591a7 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/Settings.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/Settings.kt @@ -1,8 +1,7 @@ package com.simiacryptus.skyenet.apps.plan -import com.simiacryptus.skyenet.apps.plan.PlanCoordinator.Task -import com.simiacryptus.skyenet.apps.plan.PlanCoordinator.TaskBreakdownResult import com.simiacryptus.jopenai.models.OpenAITextModel +import com.simiacryptus.skyenet.apps.plan.PlanCoordinator.TaskBreakdownResult import com.simiacryptus.skyenet.core.actors.ParsedActor data class Settings( @@ -13,6 +12,15 @@ data class Settings( val budget: Double = 2.0, val taskPlanningEnabled: Boolean = false, val shellCommandTaskEnabled: Boolean = true, + val documentationEnabled: Boolean = true, + val fileModificationEnabled: Boolean = true, + val inquiryEnabled: Boolean = true, + val codeReviewEnabled: Boolean = true, + val testGenerationEnabled: Boolean = true, + val optimizationEnabled: Boolean = true, + val securityAuditEnabled: Boolean = true, + val performanceAnalysisEnabled: Boolean = true, + val refactorTaskEnabled: Boolean = true, val autoFix: Boolean = false, val enableCommandAutoFix: Boolean = false, var commandAutoFixCommands: List = listOf(), @@ -20,15 +28,32 @@ data class Settings( val workingDir: String = ".", val language: String = if (PlanCoordinator.isWindows) "powershell" else "bash", ) { - fun getImpl(task: Task): AbstractTask { - return when (task.taskType) { - TaskType.TaskPlanning -> PlanningTask(this, task) - TaskType.Documentation -> DocumentationTask(this, task) - TaskType.FileModification -> FileModificationTask(this, task) - TaskType.RunShellCommand -> RunShellCommandTask(this, task) - TaskType.CommandAutoFix -> CommandAutoFixTask(this, task) - TaskType.Inquiry -> InquiryTask(this, task) - else -> throw RuntimeException("Unknown task type: ${task.taskType}") + fun getImpl(planTask: PlanTask): AbstractTask { + return when (planTask.taskType) { + TaskType.TaskPlanning -> if (taskPlanningEnabled) PlanningTask(this, planTask) else throw DisabledTaskException(planTask.taskType) + TaskType.Documentation -> if (documentationEnabled) DocumentationTask(this, planTask) else throw DisabledTaskException(planTask.taskType) + TaskType.FileModification -> if (fileModificationEnabled) FileModificationTask(this, planTask) else throw DisabledTaskException(planTask.taskType) + TaskType.RunShellCommand -> if (shellCommandTaskEnabled) RunShellCommandTask(this, planTask) else throw DisabledTaskException(planTask.taskType) + TaskType.CommandAutoFix -> if (enableCommandAutoFix) CommandAutoFixTask(this, planTask) else throw DisabledTaskException(planTask.taskType) + TaskType.Inquiry -> if (inquiryEnabled) InquiryTask(this, planTask) else throw DisabledTaskException(planTask.taskType) + TaskType.CodeReview -> if (codeReviewEnabled) CodeReviewTask(this, planTask) else throw DisabledTaskException(planTask.taskType) + TaskType.TestGeneration -> if (testGenerationEnabled) TestGenerationTask(this, planTask) else throw DisabledTaskException(planTask.taskType) + TaskType.Optimization -> if (optimizationEnabled) CodeOptimizationTask(this, planTask) else throw DisabledTaskException(planTask.taskType) + TaskType.SecurityAudit -> if (securityAuditEnabled) SecurityAuditTask( + this, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + TaskType.PerformanceAnalysis -> if (performanceAnalysisEnabled) PerformanceAnalysisTask( + this, + planTask + ) else throw DisabledTaskException(planTask.taskType) + + TaskType.RefactorTask -> if (refactorTaskEnabled) RefactorTask( + this, + planTask + ) else throw DisabledTaskException(planTask.taskType) + else -> throw RuntimeException("Unknown task type: ${planTask.taskType}") } } @@ -36,16 +61,16 @@ data class Settings( name = "TaskBreakdown", resultClass = TaskBreakdownResult::class.java, prompt = """ - |Given a user request, identify and list smaller, actionable tasks that can be directly implemented in code. - |Detail files input and output as well as task execution dependencies. - |Creating directories and initializing source control are out of scope. - | - |Tasks can be of the following types: - | - |${getAvailableTaskTypes().joinToString("\n") { "* ${it.promptSegment()}" }} - | - |${if (taskPlanningEnabled) "Do not start your plan with a plan to plan!\n" else ""} - """.trimMargin(), + |Given a user request, identify and list smaller, actionable tasks that can be directly implemented in code. + |Detail files input and output as well as task execution dependencies. + |Creating directories and initializing source control are out of scope. + | + |Tasks can be of the following types: + | + |${getAvailableTaskTypes().joinToString("\n") { "* ${it.promptSegment()}" }} + | + |${if (taskPlanningEnabled) "Do not start your plan with a plan to plan!\n" else ""} + """.trimMargin(), model = this.model, parsingModel = this.parsingModel, temperature = this.temperature, @@ -55,7 +80,17 @@ data class Settings( when (it) { TaskType.TaskPlanning -> this.taskPlanningEnabled TaskType.RunShellCommand -> this.shellCommandTaskEnabled - else -> true + TaskType.Documentation -> this.documentationEnabled + TaskType.FileModification -> this.fileModificationEnabled + TaskType.Inquiry -> this.inquiryEnabled + TaskType.CodeReview -> this.codeReviewEnabled + TaskType.TestGeneration -> this.testGenerationEnabled + TaskType.Optimization -> this.optimizationEnabled + TaskType.CommandAutoFix -> this.enableCommandAutoFix + TaskType.SecurityAudit -> this.securityAuditEnabled + TaskType.PerformanceAnalysis -> this.performanceAnalysisEnabled + TaskType.RefactorTask -> this.refactorTaskEnabled } - }.map { this.getImpl(Task(taskType = it)) } -} \ No newline at end of file + }.map { this.getImpl(PlanTask(taskType = it)) } +} +class DisabledTaskException(taskType: TaskType) : Exception("Task type $taskType is disabled") \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt index c6cf1636..09f6186b 100644 --- a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TaskType.kt @@ -7,4 +7,10 @@ enum class TaskType { Documentation, RunShellCommand, CommandAutoFix, + CodeReview, + TestGeneration, + Optimization, + SecurityAudit, + PerformanceAnalysis, + RefactorTask, } \ No newline at end of file diff --git a/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt new file mode 100644 index 00000000..a9f54795 --- /dev/null +++ b/webui/src/main/kotlin/com/simiacryptus/skyenet/apps/plan/TestGenerationTask.kt @@ -0,0 +1,69 @@ +package com.simiacryptus.skyenet.apps.plan + +import org.slf4j.LoggerFactory + +class TestGenerationTask( + settings: Settings, + planTask: PlanTask +) : AbstractAnalysisTask(settings, planTask) { + override val actorName: String = "TestGeneration" + override val actorPrompt: String = """ + Generate comprehensive unit tests for the provided code files. The tests should: + 1. Cover all public methods and functions + 2. Include both positive and negative test cases + |3. Test edge cases and boundary conditions + |4. Ensure high code coverage + |5. Follow best practices for unit testing in the given programming language + | + |Provide the generated tests as complete, runnable code files. + |Use appropriate testing frameworks and assertion libraries for the target language. + |Include setup and teardown methods if necessary. + |Add comments explaining the purpose of each test case. + | + |Response format: + |- Use ${TRIPLE_TILDE} code blocks with a header specifying the new test file path. + |- Specify the language for syntax highlighting after the opening triple backticks. + |- Separate code blocks with a single blank line. + | + |Example: + | + |Here are the generated unit tests: + | + |### src/test/java/com/example/UtilsTest.java + |${TRIPLE_TILDE}java + |import org.junit.jupiter.api.Test; + |import static org.junit.jupiter.api.Assertions.*; + | + |public class UtilsTest { + | @Test + | public void testExampleFunction() { + | // Test the happy path + | assertEquals(3, Utils.exampleFunction(1, 2)); + | + | // Test edge cases + | assertEquals(0, Utils.exampleFunction(0, 0)); + | assertEquals(-1, Utils.exampleFunction(-1, 0)); + | + | // Test error conditions + | assertThrows(IllegalArgumentException.class, () -> { + | Utils.exampleFunction(Integer.MAX_VALUE, 1); + | }); + | } + |} + ${TRIPLE_TILDE} + """.trimMargin() + + override fun getAnalysisInstruction(): String = "Generate tests for the following code" + + override fun promptSegment(): String { + return """ + |TestGeneration - Generate unit tests for the specified code files + | ** Specify the files for which tests should be generated + | ** List input files/tasks to be examined when generating tests + """.trimMargin() + } + + companion object { + private val log = LoggerFactory.getLogger(TestGenerationTask::class.java) + } +} \ No newline at end of file