diff --git a/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/StatusCommand.kt b/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/StatusCommand.kt index ed3331e0..c7893084 100644 --- a/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/StatusCommand.kt +++ b/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/StatusCommand.kt @@ -196,7 +196,7 @@ class StatusCommand( terminal.println() terminal.println(bold("📁 Workspace")) val workspace = link.socket.ampere.agents.environment.workspace.defaultWorkspace() - val workspacePath = workspace?.baseDirectory ?: "disabled" + val workspacePath = workspace.baseDirectory terminal.println(" ${cyan(workspacePath)} ${gray("(watching)")}") } diff --git a/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/cli/coordination/CoordinationPresenter.kt b/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/cli/coordination/CoordinationPresenter.kt index c6170fd2..4b38902e 100644 --- a/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/cli/coordination/CoordinationPresenter.kt +++ b/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/cli/coordination/CoordinationPresenter.kt @@ -50,6 +50,13 @@ data class CoordinationViewState( val focusedAgentId: AgentId?, ) +private data class PresenterControls( + val subMode: CoordinationSubMode, + val verbose: Boolean, + val focusedAgentId: AgentId?, + val selectedMeetingId: String?, +) + /** * Provider interface for coordination state. * @@ -119,29 +126,33 @@ class CoordinationPresenter( init { // Combine coordination state and agent states to produce view state collectJob = scope.launch { - combine( - coordinationStateProvider.state, - agentStateProvider.agentStates, + val controls = combine( _subMode, _verbose, _focusedAgentId, _selectedMeetingId, - ) { flows: Array -> - val coordinationState = flows[0] as CoordinationState - val agentStates = flows[1] as Map - val subMode = flows[2] as CoordinationSubMode - val verbose = flows[3] as Boolean - val focusedAgentId = flows[4] as AgentId? - val selectedMeetingId = flows[5] as String? - - updateViewState( - coordinationState = coordinationState, - agentStates = agentStates, + ) { subMode, verbose, focusedAgentId, selectedMeetingId -> + PresenterControls( subMode = subMode, verbose = verbose, focusedAgentId = focusedAgentId, selectedMeetingId = selectedMeetingId, ) + } + + combine( + coordinationStateProvider.state, + agentStateProvider.agentStates, + controls, + ) { coordinationState, agentStates, controls -> + updateViewState( + coordinationState = coordinationState, + agentStates = agentStates, + subMode = controls.subMode, + verbose = controls.verbose, + focusedAgentId = controls.focusedAgentId, + selectedMeetingId = controls.selectedMeetingId, + ) }.collect { newState -> _viewState.value = newState } diff --git a/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/cli/coordination/TopologyLayoutCalculator.kt b/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/cli/coordination/TopologyLayoutCalculator.kt index 12d92858..db51fcd6 100644 --- a/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/cli/coordination/TopologyLayoutCalculator.kt +++ b/ampere-cli/src/jvmMain/kotlin/link/socket/ampere/cli/coordination/TopologyLayoutCalculator.kt @@ -232,12 +232,11 @@ class TopologyLayoutCalculator { val reverseEdge = state.edges.find { it.sourceAgentId == target && it.targetAgentId == source } - val isBidirectional = reverseEdge != null // Combine interaction types from both directions if bidirectional - val interactionTypes = if (isBidirectional) { - coordinationEdge.interactionTypes + (reverseEdge?.interactionTypes ?: emptySet()) + val interactionTypes = if (reverseEdge != null) { + coordinationEdge.interactionTypes + reverseEdge.interactionTypes } else { coordinationEdge.interactionTypes } diff --git a/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/cli/render/WaveformCellAdapterTest.kt b/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/cli/render/WaveformCellAdapterTest.kt index 2c02a84d..defc1563 100644 --- a/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/cli/render/WaveformCellAdapterTest.kt +++ b/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/cli/render/WaveformCellAdapterTest.kt @@ -76,8 +76,8 @@ class WaveformCellAdapterTest { val cell = buffer.getCell(0, 0) assertEquals('#', cell.char) - assertNotNull(cell.ansiColor) - assertTrue(cell.ansiColor!!.contains("38;5;196")) + val ansiColor = assertNotNull(cell.ansiColor) + assertTrue(ansiColor.contains("38;5;196")) } @Test diff --git a/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/cli/watch/presentation/SparkPresentationTest.kt b/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/cli/watch/presentation/SparkPresentationTest.kt index fa11a234..24e083e8 100644 --- a/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/cli/watch/presentation/SparkPresentationTest.kt +++ b/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/cli/watch/presentation/SparkPresentationTest.kt @@ -3,6 +3,7 @@ package link.socket.ampere.cli.watch.presentation import kotlinx.datetime.Instant import org.junit.jupiter.api.Test import kotlin.test.assertEquals +import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -54,10 +55,10 @@ class SparkPresentationTest { assertEquals(1, history.size) assertEquals(SparkTransitionDirection.REMOVED, history.first().direction) - val state = collector.getCognitiveState(agentId) - assertEquals("OPERATIONAL", state?.affinityName) - assertTrue(state?.sparkNames?.isEmpty() == true) - assertEquals(0, state?.depth) + val state = assertNotNull(collector.getCognitiveState(agentId)) + assertEquals("OPERATIONAL", state.affinityName) + assertTrue(state.sparkNames.isEmpty()) + assertEquals(0, state.depth) } @Test diff --git a/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/config/McpConfigTest.kt b/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/config/McpConfigTest.kt index 826ac853..ae42b356 100644 --- a/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/config/McpConfigTest.kt +++ b/ampere-cli/src/jvmTest/kotlin/link/socket/ampere/config/McpConfigTest.kt @@ -44,10 +44,10 @@ class McpConfigTest { val config = ConfigParser.parse(yaml) - assertNotNull(config.mcp) - assertEquals(2, config.mcp!!.servers.size) + val mcp = assertNotNull(config.mcp) + assertEquals(2, mcp.servers.size) - val github = config.mcp!!.servers[0] + val github = mcp.servers[0] assertEquals("github", github.id) assertEquals("GitHub CLI", github.name) assertEquals("stdio", github.protocol) @@ -55,7 +55,7 @@ class McpConfigTest { assertNull(github.authToken) assertEquals("act-with-notification", github.autonomy) - val database = config.mcp!!.servers[1] + val database = mcp.servers[1] assertEquals("database", database.id) assertEquals("Database Service", database.name) assertEquals("http", database.protocol) diff --git a/ampere-core/build.gradle.kts b/ampere-core/build.gradle.kts index 54e6990a..e70dba53 100644 --- a/ampere-core/build.gradle.kts +++ b/ampere-core/build.gradle.kts @@ -1,3 +1,4 @@ +@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) import com.vanniktech.maven.publish.JavadocJar import com.vanniktech.maven.publish.KotlinMultiplatform @@ -25,6 +26,7 @@ compose.resources { } val ampereVersion: String by project +val composeVersion = findProperty("compose.version") as String group = "link.socket" version = ampereVersion @@ -87,6 +89,10 @@ sqldelight { kotlin { applyDefaultHierarchyTemplate() + compilerOptions { + freeCompilerArgs.add("-Xexpect-actual-classes") + } + androidTarget { compilerOptions { jvmTarget.set(JvmTarget.JVM_21) @@ -132,19 +138,15 @@ kotlin { } sourceSets { - all { - languageSettings.enableLanguageFeature("ExpectActualClasses") - } - val commonMain by getting { dependencies { implementation(kotlin("reflect")) - implementation(compose.runtime) - implementation(compose.foundation) - implementation(compose.material) - implementation(compose.components.resources) - implementation(compose.materialIconsExtended) + implementation("org.jetbrains.compose.runtime:runtime:$composeVersion") + implementation("org.jetbrains.compose.foundation:foundation:$composeVersion") + implementation("org.jetbrains.compose.material:material:$composeVersion") + implementation("org.jetbrains.compose.components:components-resources:$composeVersion") + implementation("org.jetbrains.compose.material:material-icons-extended:1.7.3") implementation("ai.koog:koog-agents:0.5.4") implementation("app.cash.sqldelight:coroutines-extensions:2.2.1") @@ -166,7 +168,7 @@ kotlin { val androidMain by getting { dependencies { implementation(project(":ampere-compose")) - implementation(compose.uiTooling) + implementation("org.jetbrains.compose.ui:ui-tooling:$composeVersion") api("androidx.activity:activity-compose:1.11.0") api("androidx.appcompat:appcompat:1.7.1") @@ -178,7 +180,7 @@ kotlin { } val jvmMain by getting { dependencies { - implementation(compose.desktop.common) + implementation("org.jetbrains.compose.desktop:desktop-jvm:$composeVersion") implementation("app.cash.sqldelight:sqlite-driver:2.2.1") implementation("com.charleskorn.kaml:kaml:0.72.0") @@ -224,7 +226,9 @@ kotlin { // https://kotlinlang.org/docs/native-objc-interop.html#export-of-kdoc-comments-to-generated-objective-c-headers targets.withType { - compilations.get("main").compilerOptions.options.freeCompilerArgs.add("-Xexport-kdoc") + compilations.get("main").compileTaskProvider.configure { + compilerOptions.freeCompilerArgs.add("-Xexport-kdoc") + } } } diff --git a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/definition/AutonomousAgent.kt b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/definition/AutonomousAgent.kt index c73252b5..6a2c4d6d 100644 --- a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/definition/AutonomousAgent.kt +++ b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/definition/AutonomousAgent.kt @@ -262,7 +262,6 @@ abstract class AutonomousAgent : Agent, NeuralAgent { private var agentRuntimeScope: CoroutineScope? = null private var agentRuntimeLoopJob: Job? = null - @Transient private val phaseSparkManager: PhaseSparkManager by lazy(LazyThreadSafetyMode.NONE) { PhaseSparkManager.create(this, agentConfiguration.cognitiveConfig.phaseSparks) } diff --git a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/events/escalation/EscalationDecision.kt b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/events/escalation/EscalationDecision.kt index 90fae6e2..472d69d3 100644 --- a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/events/escalation/EscalationDecision.kt +++ b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/events/escalation/EscalationDecision.kt @@ -28,10 +28,3 @@ data class EscalationDecision( CRITICAL, } } - -/** - * Compare urgency levels for ordering. - */ -internal operator fun EscalationDecision.UrgencyLevel.compareTo( - other: EscalationDecision.UrgencyLevel, -): Int = this.ordinal.compareTo(other.ordinal) diff --git a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/executor/FunctionExecutor.kt b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/executor/FunctionExecutor.kt index 06707d71..1b4ef436 100644 --- a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/executor/FunctionExecutor.kt +++ b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/executor/FunctionExecutor.kt @@ -90,7 +90,7 @@ class FunctionExecutor( executorId = id, timestamp = Clock.System.now(), result = createFailureOutcome( - request = request as ExecutionRequest, + request = request, startTime = startTime, error = ExecutionError( type = ExecutionError.Type.TOOL_UNAVAILABLE, @@ -126,11 +126,7 @@ class FunctionExecutor( logger.i { "Executing FunctionTool '${tool.name}'" } // Execute the tool - // Cast is safe because we validated tool is FunctionTool above - @Suppress("UNCHECKED_CAST") - val outcome = (tool as FunctionTool).execute( - request as ExecutionRequest, - ) + val outcome = tool.executeWith(request) // Emit appropriate status based on outcome type when (outcome) { @@ -154,25 +150,7 @@ class FunctionExecutor( ), ) } - else -> { - logger.w { "Unexpected outcome type: ${outcome::class.simpleName}" } - emit( - ExecutionStatus.Failed( - executorId = id, - timestamp = Clock.System.now(), - result = createFailureOutcome( - request = request as ExecutionRequest, - startTime = startTime, - error = ExecutionError( - type = ExecutionError.Type.UNEXPECTED, - message = "Unexpected outcome type", - details = outcome::class.simpleName, - isRetryable = false, - ), - ), - ), - ) - } + else -> emitUnexpectedOutcomeFailure(request, startTime, outcome) } } catch (e: Exception) { // Catch any unexpected exceptions and convert to Failed status @@ -182,7 +160,7 @@ class FunctionExecutor( executorId = id, timestamp = Clock.System.now(), result = createFailureOutcome( - request = request as ExecutionRequest, + request = request, startTime = startTime, error = ExecutionError( type = ExecutionError.Type.UNEXPECTED, @@ -203,7 +181,7 @@ class FunctionExecutor( * different error scenarios. */ private fun createFailureOutcome( - request: ExecutionRequest, + request: ExecutionRequest<*>, startTime: kotlinx.datetime.Instant, error: ExecutionError, ): ExecutionOutcome.NoChanges.Failure { @@ -217,6 +195,37 @@ class FunctionExecutor( ) } + private suspend fun FunctionTool<*>.executeWith( + request: ExecutionRequest<*>, + ): link.socket.ampere.agents.domain.outcome.Outcome { + @Suppress("UNCHECKED_CAST") + return (this as FunctionTool).execute(request as ExecutionRequest) + } + + private suspend fun kotlinx.coroutines.flow.FlowCollector.emitUnexpectedOutcomeFailure( + request: ExecutionRequest<*>, + startTime: kotlinx.datetime.Instant, + outcome: link.socket.ampere.agents.domain.outcome.Outcome, + ) { + logger.w { "Unexpected outcome type: ${outcome::class.simpleName}" } + emit( + ExecutionStatus.Failed( + executorId = id, + timestamp = Clock.System.now(), + result = createFailureOutcome( + request = request, + startTime = startTime, + error = ExecutionError( + type = ExecutionError.Type.UNEXPECTED, + message = "Unexpected outcome type", + details = outcome::class.simpleName, + isRetryable = false, + ), + ), + ), + ) + } + companion object { /** * Default function executor ID diff --git a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/executor/McpExecutor.kt b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/executor/McpExecutor.kt index 6fbdda25..3eb0e770 100644 --- a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/executor/McpExecutor.kt +++ b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/executor/McpExecutor.kt @@ -13,7 +13,6 @@ import link.socket.ampere.agents.domain.outcome.ExecutionOutcome import link.socket.ampere.agents.domain.status.ExecutionStatus import link.socket.ampere.agents.domain.task.Task import link.socket.ampere.agents.events.bus.EventSerialBus -import link.socket.ampere.agents.execution.request.ExecutionContext import link.socket.ampere.agents.execution.request.ExecutionRequest import link.socket.ampere.agents.execution.tools.McpTool import link.socket.ampere.agents.execution.tools.Tool @@ -127,7 +126,7 @@ class McpExecutor( executorId = id, timestamp = Clock.System.now(), result = createFailureOutcome( - request = request as ExecutionRequest, + request = request, startTime = startTime, error = ExecutionError( type = ExecutionError.Type.TOOL_UNAVAILABLE, @@ -158,7 +157,7 @@ class McpExecutor( executorId = id, timestamp = Clock.System.now(), result = createFailureOutcome( - request = request as ExecutionRequest, + request = request, startTime = startTime, error = ExecutionError( type = ExecutionError.Type.TOOL_UNAVAILABLE, @@ -181,7 +180,7 @@ class McpExecutor( executorId = id, timestamp = Clock.System.now(), result = createFailureOutcome( - request = request as ExecutionRequest, + request = request, startTime = startTime, error = ExecutionError( type = ExecutionError.Type.TOOL_UNAVAILABLE, @@ -202,7 +201,7 @@ class McpExecutor( executorId = id, timestamp = Clock.System.now(), result = createFailureOutcome( - request = request as ExecutionRequest, + request = request, startTime = startTime, error = ExecutionError( type = ExecutionError.Type.TOOL_UNAVAILABLE, @@ -241,7 +240,7 @@ class McpExecutor( // Translate result to outcome val outcome = translateToolCallResultToOutcome( toolCallResult = toolCallResult, - request = request as ExecutionRequest, + request = request, startTime = startTime, ) @@ -270,25 +269,7 @@ class McpExecutor( ), ) } - else -> { - logger.w { "Unexpected outcome type: ${outcome::class.simpleName}" } - emit( - ExecutionStatus.Failed( - executorId = id, - timestamp = Clock.System.now(), - result = createFailureOutcome( - request = request as ExecutionRequest, - startTime = startTime, - error = ExecutionError( - type = ExecutionError.Type.UNEXPECTED, - message = "Unexpected outcome type", - details = outcome::class.simpleName, - isRetryable = false, - ), - ), - ), - ) - } + else -> emitUnexpectedOutcomeFailure(request, startTime, outcome) } }.onFailure { error -> logger.e(error) { "Failed to invoke MCP tool '${tool.name}'" } @@ -297,7 +278,7 @@ class McpExecutor( executorId = id, timestamp = Clock.System.now(), result = createFailureOutcome( - request = request as ExecutionRequest, + request = request, startTime = startTime, error = ExecutionError( type = ExecutionError.Type.UNEXPECTED, @@ -317,7 +298,7 @@ class McpExecutor( executorId = id, timestamp = Clock.System.now(), result = createFailureOutcome( - request = request as ExecutionRequest, + request = request, startTime = startTime, error = ExecutionError( type = ExecutionError.Type.UNEXPECTED, @@ -398,7 +379,7 @@ class McpExecutor( */ private fun translateToolCallResultToOutcome( toolCallResult: ToolCallResult, - request: ExecutionRequest, + request: ExecutionRequest<*>, startTime: kotlinx.datetime.Instant, ): ExecutionOutcome { val endTime = Clock.System.now() @@ -443,7 +424,7 @@ class McpExecutor( * different error scenarios. */ private fun createFailureOutcome( - request: ExecutionRequest, + request: ExecutionRequest<*>, startTime: kotlinx.datetime.Instant, error: ExecutionError, ): ExecutionOutcome.NoChanges.Failure { @@ -457,6 +438,30 @@ class McpExecutor( ) } + private suspend fun kotlinx.coroutines.flow.FlowCollector.emitUnexpectedOutcomeFailure( + request: ExecutionRequest<*>, + startTime: kotlinx.datetime.Instant, + outcome: ExecutionOutcome, + ) { + logger.w { "Unexpected outcome type: ${outcome::class.simpleName}" } + emit( + ExecutionStatus.Failed( + executorId = id, + timestamp = Clock.System.now(), + result = createFailureOutcome( + request = request, + startTime = startTime, + error = ExecutionError( + type = ExecutionError.Type.UNEXPECTED, + message = "Unexpected outcome type", + details = outcome::class.simpleName, + isRetryable = false, + ), + ), + ), + ) + } + companion object { /** * Default MCP executor ID diff --git a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/tools/invoke/ToolInvoker.kt b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/tools/invoke/ToolInvoker.kt index c37ccf3f..299aee5c 100644 --- a/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/tools/invoke/ToolInvoker.kt +++ b/ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/tools/invoke/ToolInvoker.kt @@ -107,7 +107,6 @@ class ToolInvoker( is ExecutionOutcome.NoChanges.Failure -> outcome.message is ExecutionOutcome.CodeReading.Failure -> outcome.error.message is ExecutionOutcome.CodeChanged.Failure -> outcome.error.message - else -> "Tool execution failed" }, toolId = tool.id, duration = duration, diff --git a/ampere-core/src/commonMain/kotlin/link/socket/ampere/ui/conversation/ChatMessage.kt b/ampere-core/src/commonMain/kotlin/link/socket/ampere/ui/conversation/ChatMessage.kt index cbdf117a..b911d6d6 100644 --- a/ampere-core/src/commonMain/kotlin/link/socket/ampere/ui/conversation/ChatMessage.kt +++ b/ampere-core/src/commonMain/kotlin/link/socket/ampere/ui/conversation/ChatMessage.kt @@ -243,6 +243,7 @@ fun ChatMessage( } } + @Suppress("DEPRECATION") val clipboardManager = LocalClipboardManager.current IconButton( diff --git a/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/definition/CodeAgentIssueDiscoveryTest.kt b/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/definition/CodeAgentIssueDiscoveryTest.kt index 41c50a24..7d6688d0 100644 --- a/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/definition/CodeAgentIssueDiscoveryTest.kt +++ b/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/definition/CodeAgentIssueDiscoveryTest.kt @@ -2,6 +2,7 @@ package link.socket.ampere.agents.definition import kotlin.test.Test import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -152,9 +153,8 @@ class CodeAgentIssueDiscoveryTest { val result = agent.queryAssignedIssues() // Verify query parameters - val lastQuery = mockProvider.lastQuery - assertTrue(lastQuery != null, "Query should have been called") - assertTrue(lastQuery!!.state == IssueState.Open, "Should query for open issues") + val lastQuery = assertNotNull(mockProvider.lastQuery, "Query should have been called") + assertTrue(lastQuery.state == IssueState.Open, "Should query for open issues") assertTrue(lastQuery.assignee == "CodeWriterAgent", "Should query for CodeWriterAgent") assertTrue(lastQuery.limit == 20, "Should limit to 20 results") @@ -211,9 +211,8 @@ class CodeAgentIssueDiscoveryTest { val result = agent.queryAvailableIssues() // Verify query parameters - val lastQuery = mockProvider.lastQuery - assertTrue(lastQuery != null, "Query should have been called") - assertTrue(lastQuery!!.state == IssueState.Open, "Should query for open issues") + val lastQuery = assertNotNull(mockProvider.lastQuery, "Query should have been called") + assertTrue(lastQuery.state == IssueState.Open, "Should query for open issues") assertTrue(lastQuery.assignee == null, "Should query for unassigned issues") assertTrue(lastQuery.labels.contains("code"), "Should filter by 'code' label") assertTrue(lastQuery.labels.contains("task"), "Should filter by 'task' label") diff --git a/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/execution/tools/invoke/ToolInvokerTest.kt b/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/execution/tools/invoke/ToolInvokerTest.kt index 2f4e1900..ff50ef8c 100644 --- a/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/execution/tools/invoke/ToolInvokerTest.kt +++ b/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/execution/tools/invoke/ToolInvokerTest.kt @@ -114,8 +114,8 @@ class ToolInvokerTest { assertIs(result) assertEquals("test-tool-1", result.toolId) assertIs(result.outcome) - assertIs(result.outcome) - assertEquals("hello world", (result.outcome as ExecutionOutcome.NoChanges.Success).message) + val outcome = assertIs(result.outcome) + assertEquals("hello world", outcome.message) } /** @@ -324,13 +324,13 @@ class ToolInvokerTest { // Verify results are correct and isolated assertIs(result1) assertEquals("test-tool-6a", result1.toolId) - assertIs(result1.outcome) - assertEquals("result-a", (result1.outcome as ExecutionOutcome.NoChanges.Success).message) + val outcome1 = assertIs(result1.outcome) + assertEquals("result-a", outcome1.message) assertIs(result2) assertEquals("test-tool-6b", result2.toolId) - assertIs(result2.outcome) - assertEquals("result-b", (result2.outcome as ExecutionOutcome.NoChanges.Success).message) + val outcome2 = assertIs(result2.outcome) + assertEquals("result-b", outcome2.message) } /** @@ -422,7 +422,7 @@ class ToolInvokerTest { val result = invoker.invoke(request) assertIs(result) - assertIs(result.outcome) - assertEquals(2, (result.outcome as ExecutionOutcome.CodeChanged.Success).changedFiles.size) + val outcome = assertIs(result.outcome) + assertEquals(2, outcome.changedFiles.size) } } diff --git a/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/implementations/code/RunLLMToExecuteToolTest.kt b/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/implementations/code/RunLLMToExecuteToolTest.kt index 71c22366..c82ba173 100644 --- a/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/implementations/code/RunLLMToExecuteToolTest.kt +++ b/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/implementations/code/RunLLMToExecuteToolTest.kt @@ -218,7 +218,6 @@ class RunLLMToExecuteToolTest { // Verify the function exists and has the correct signature val function: (Tool<*>, ExecutionRequest<*>) -> ExecutionOutcome = agent.runLLMToExecuteTool - // Verify it's not null and is a function - assertTrue(function != null, "runLLMToExecuteTool should not be null") + assertTrue(function.toString().isNotBlank(), "runLLMToExecuteTool should be callable") } } diff --git a/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/tools/ToolInitializerTest.kt b/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/tools/ToolInitializerTest.kt index 42bf42c0..eb212034 100644 --- a/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/tools/ToolInitializerTest.kt +++ b/ampere-core/src/commonTest/kotlin/link/socket/ampere/agents/tools/ToolInitializerTest.kt @@ -4,6 +4,7 @@ import co.touchlab.kermit.Logger import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlinx.coroutines.CoroutineScope @@ -322,8 +323,7 @@ class ToolInitializerTest { @Test fun `WriteCode tool execution returns success outcome`() = runTest { val tools = createLocalToolSet() - val writeCodeTool = tools.find { it.id == "write_code" } as? FunctionTool - assertNotNull(writeCodeTool, "WriteCode tool should exist") + val writeCodeTool = tools.requireFunctionTool("write_code") val context = ExecutionContext.Code.WriteCode( executorId = "test-executor", @@ -336,23 +336,24 @@ class ToolInitializerTest { instructionsPerFilePath = listOf("test.kt" to "// Test code"), ) - val outcome = writeCodeTool!!.execute( + val outcome = writeCodeTool.execute( ExecutionRequest( context = context, constraints = ExecutionConstraints(), ), ) - assertTrue(outcome is ExecutionOutcome.CodeChanged.Success, "Should return CodeChanged.Success") - val success = outcome as ExecutionOutcome.CodeChanged.Success + val success = assertIs( + outcome, + "Should return CodeChanged.Success", + ) assertTrue(success.changedFiles.isNotEmpty(), "Should have changed files") } @Test fun `ReadCode tool execution returns success outcome`() = runTest { val tools = createLocalToolSet() - val readCodeTool = tools.find { it.id == "read_code" } as? FunctionTool - assertNotNull(readCodeTool, "ReadCode tool should exist") + val readCodeTool = tools.requireFunctionTool("read_code") val context = ExecutionContext.Code.ReadCode( executorId = "test-executor", @@ -365,53 +366,62 @@ class ToolInitializerTest { filePathsToRead = listOf("test.kt", "main.kt"), ) - val outcome = readCodeTool!!.execute( + val outcome = readCodeTool.execute( ExecutionRequest( context = context, constraints = ExecutionConstraints(), ), ) - assertTrue(outcome is ExecutionOutcome.CodeReading.Success, "Should return CodeReading.Success") - val success = outcome as ExecutionOutcome.CodeReading.Success + val success = assertIs( + outcome, + "Should return CodeReading.Success", + ) assertEquals(2, success.readFiles.size, "Should have read 2 files") } @Test fun `AskHuman tool execution returns success outcome`() = runTest { val tools = createLocalToolSet() - val askHumanTool = tools.find { it.id == "ask_human" } as? FunctionTool - assertNotNull(askHumanTool, "AskHuman tool should exist") + val askHumanTool = tools.requireFunctionTool("ask_human") - val outcome = askHumanTool!!.execute(createTestExecutionRequest()) + val outcome = askHumanTool.execute(createTestExecutionRequest()) - assertTrue(outcome is ExecutionOutcome.NoChanges.Success, "Should return NoChanges.Success") + assertIs(outcome, "Should return NoChanges.Success") } @Test fun `CreateTicket tool execution returns success outcome`() = runTest { val tools = createLocalToolSet() - val createTicketTool = tools.find { it.id == "create_ticket" } as? FunctionTool - assertNotNull(createTicketTool, "CreateTicket tool should exist") + val createTicketTool = tools.requireFunctionTool("create_ticket") - val outcome = createTicketTool!!.execute(createTestExecutionRequest()) + val outcome = createTicketTool.execute(createTestExecutionRequest()) - assertTrue(outcome is ExecutionOutcome.NoChanges.Success, "Should return NoChanges.Success") + assertIs(outcome, "Should return NoChanges.Success") } @Test fun `RunTests tool execution returns success outcome`() = runTest { val tools = createLocalToolSet() - val runTestsTool = tools.find { it.id == "run_tests" } as? FunctionTool - assertNotNull(runTestsTool, "RunTests tool should exist") + val runTestsTool = tools.requireFunctionTool("run_tests") - val outcome = runTestsTool!!.execute(createTestExecutionRequest()) + val outcome = runTestsTool.execute(createTestExecutionRequest()) - assertTrue(outcome is ExecutionOutcome.NoChanges.Success, "Should return NoChanges.Success") + assertIs(outcome, "Should return NoChanges.Success") } // ==================== HELPER FUNCTIONS ==================== + @Suppress("UNCHECKED_CAST") + private fun List>.requireFunctionTool( + id: String, + ): FunctionTool { + return assertNotNull( + find { it.id == id }, + "$id tool should exist", + ) as FunctionTool + } + /** * Creates a test logger that captures output for verification. */ diff --git a/ampere-core/src/commonTest/kotlin/link/socket/ampere/coordination/InteractionClassifierTest.kt b/ampere-core/src/commonTest/kotlin/link/socket/ampere/coordination/InteractionClassifierTest.kt index 469fd47d..b2f711f6 100644 --- a/ampere-core/src/commonTest/kotlin/link/socket/ampere/coordination/InteractionClassifierTest.kt +++ b/ampere-core/src/commonTest/kotlin/link/socket/ampere/coordination/InteractionClassifierTest.kt @@ -294,9 +294,9 @@ class InteractionClassifierTest { val interaction = classifier.classify(event) assertNotNull(interaction) - assertNotNull(interaction.context) - assertEquals(100, interaction.context!!.length, "Context should be truncated to 100 chars") - assertEquals("...", interaction.context!!.takeLast(3), "Truncated context should end with ...") + val context = assertNotNull(interaction.context) + assertEquals(100, context.length, "Context should be truncated to 100 chars") + assertEquals("...", context.takeLast(3), "Truncated context should end with ...") } @Test diff --git a/ampere-core/src/commonTest/kotlin/link/socket/ampere/integrations/issues/IssueTrackerProviderTest.kt b/ampere-core/src/commonTest/kotlin/link/socket/ampere/integrations/issues/IssueTrackerProviderTest.kt index 6d621eaa..1f544dde 100644 --- a/ampere-core/src/commonTest/kotlin/link/socket/ampere/integrations/issues/IssueTrackerProviderTest.kt +++ b/ampere-core/src/commonTest/kotlin/link/socket/ampere/integrations/issues/IssueTrackerProviderTest.kt @@ -3,7 +3,7 @@ package link.socket.ampere.integrations.issues import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNull -import kotlin.test.assertTrue +import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.Json import link.socket.ampere.agents.execution.tools.issue.CreatedIssue import link.socket.ampere.agents.execution.tools.issue.IssueCreateRequest @@ -251,12 +251,12 @@ class IssueTrackerProviderTest { } } - // Verify interface supports Result return types for error handling - assertTrue(mockProvider is IssueTrackerProvider) + val provider: IssueTrackerProvider = mockProvider + assertEquals("test", provider.providerId) } @Test - fun `resolvedDependencies allows dependency injection during batch creation`() { + fun `resolvedDependencies allows dependency injection during batch creation`() = runTest { // Simulate resolving dependencies val dependencies = mapOf( "task-1" to 100, @@ -318,7 +318,14 @@ class IssueTrackerProviderTest { dependsOn = listOf("task-1", "task-2"), ) - // Verify the interface supports dependency injection - assertTrue(mockProvider is IssueTrackerProvider) + val provider: IssueTrackerProvider = mockProvider + val created = provider.createIssue( + repository = "owner/repo", + request = request, + resolvedDependencies = dependencies, + ).getOrThrow() + + assertEquals(102, created.issueNumber) + assertEquals(99, created.parentIssueNumber) } } diff --git a/ampere-core/src/commonTest/kotlin/link/socket/ampere/tools/KnowledgeQueryToolTest.kt b/ampere-core/src/commonTest/kotlin/link/socket/ampere/tools/KnowledgeQueryToolTest.kt index f7d37573..935ab9a1 100644 --- a/ampere-core/src/commonTest/kotlin/link/socket/ampere/tools/KnowledgeQueryToolTest.kt +++ b/ampere-core/src/commonTest/kotlin/link/socket/ampere/tools/KnowledgeQueryToolTest.kt @@ -58,8 +58,8 @@ class KnowledgeQueryToolTest { val manifest = manifest("pl-1", PluginPermission.KnowledgeQuery("work")) val tool = KnowledgeQueryTool(store = store, pluginManifest = manifest) - assertNotNull(tool.pluginManifest) - assertEquals("pl-1", tool.pluginManifest?.id) + val pluginManifest = assertNotNull(tool.pluginManifest) + assertEquals("pl-1", pluginManifest.id) } @Test diff --git a/ampere-core/src/jvmMain/kotlin/link/socket/ampere/Main.jvm.kt b/ampere-core/src/jvmMain/kotlin/link/socket/ampere/Main.jvm.kt index d9cba3b0..4ad08178 100644 --- a/ampere-core/src/jvmMain/kotlin/link/socket/ampere/Main.jvm.kt +++ b/ampere-core/src/jvmMain/kotlin/link/socket/ampere/Main.jvm.kt @@ -1,10 +1,10 @@ package link.socket.ampere -import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import app.cash.sqldelight.db.SqlDriver import link.socket.ampere.data.createJvmDriver import link.socket.ampere.ui.App diff --git a/ampere-core/src/jvmMain/kotlin/link/socket/ampere/ui/agent/AgentCreationScreenPreview.kt b/ampere-core/src/jvmMain/kotlin/link/socket/ampere/ui/agent/AgentCreationScreenPreview.kt index e1a20f67..8912d338 100644 --- a/ampere-core/src/jvmMain/kotlin/link/socket/ampere/ui/agent/AgentCreationScreenPreview.kt +++ b/ampere-core/src/jvmMain/kotlin/link/socket/ampere/ui/agent/AgentCreationScreenPreview.kt @@ -1,7 +1,7 @@ package link.socket.ampere.ui.agent -import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview @Preview @Composable diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/data/MeetingRepositoryTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/data/MeetingRepositoryTest.kt index 0ef656cf..6f8c4396 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/data/MeetingRepositoryTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/data/MeetingRepositoryTest.kt @@ -205,9 +205,9 @@ class MeetingRepositoryTest { val completedStatus = loaded.status assertEquals(2, completedStatus.attendedBy.size) - assertNotNull(completedStatus.outcomes) - assertEquals(1, completedStatus.outcomes!!.size) - assertIs(completedStatus.outcomes!![0]) + val outcomes = assertNotNull(completedStatus.outcomes) + assertEquals(1, outcomes.size) + assertIs(outcomes[0]) } } @Test @@ -263,12 +263,12 @@ class MeetingRepositoryTest { val loaded = repo.getMeeting(meeting.id).getOrNull() assertNotNull(loaded) - assertIs(loaded.status) - val status = loaded.status as MeetingStatus.Completed + val status = assertIs(loaded.status) assertEquals(2, status.attendedBy.size) - assertEquals(1, status.outcomes?.size) - assertIs(status.outcomes!![0]) + val outcomes = assertNotNull(status.outcomes) + assertEquals(1, outcomes.size) + assertIs(outcomes[0]) } } @Test diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/AgentLearningLoopIntegrationTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/AgentLearningLoopIntegrationTest.kt index b0f5a6ff..f2fd585d 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/AgentLearningLoopIntegrationTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/AgentLearningLoopIntegrationTest.kt @@ -4,6 +4,7 @@ import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.test.assertIs import kotlin.test.assertTrue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -189,11 +190,11 @@ class AgentLearningLoopIntegrationTest { val failureOutcome = stubFailureOutcome() val failureKnowledge = productAgent.extractKnowledgeFromOutcome(failureOutcome, task, plan) - assertTrue(successKnowledge is Knowledge.FromOutcome) - assertTrue(failureKnowledge is Knowledge.FromOutcome) - assertTrue(successKnowledge.approach.isNotEmpty()) - assertTrue(failureKnowledge.approach.isNotEmpty()) - assertTrue(successKnowledge.learnings.contains("Success")) - assertTrue(failureKnowledge.learnings.contains("Failure")) + val successFromOutcome = assertIs(successKnowledge) + val failureFromOutcome = assertIs(failureKnowledge) + assertTrue(successFromOutcome.approach.isNotEmpty()) + assertTrue(failureFromOutcome.approach.isNotEmpty()) + assertTrue(successFromOutcome.learnings.contains("Success")) + assertTrue(failureFromOutcome.learnings.contains("Failure")) } } diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/ProductManagerAgentTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/ProductManagerAgentTest.kt index 91204f74..0693a226 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/ProductManagerAgentTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/ProductManagerAgentTest.kt @@ -4,6 +4,7 @@ import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.test.assertIs import kotlin.test.assertTrue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -136,9 +137,9 @@ class ProductManagerAgentTest { val knowledge = productAgent.extractKnowledgeFromOutcome(outcome, task, plan) - assertTrue(knowledge.approach.isNotEmpty(), "Approach should be captured") - assertTrue(knowledge.learnings.contains("Success"), "Learnings should mention success") - assertTrue(knowledge is Knowledge.FromOutcome) + val fromOutcome = assertIs(knowledge) + assertTrue(fromOutcome.approach.isNotEmpty(), "Approach should be captured") + assertTrue(fromOutcome.learnings.contains("Success"), "Learnings should mention success") } @Test diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/memory/AgentMemoryServiceTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/memory/AgentMemoryServiceTest.kt index ff65075a..2ab9d5f7 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/memory/AgentMemoryServiceTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/memory/AgentMemoryServiceTest.kt @@ -7,6 +7,7 @@ import kotlin.test.AfterTest import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -535,15 +536,14 @@ class AgentMemoryServiceTest { // Verify event was emitted assertEquals(1, emittedEvents.size) val event = emittedEvents.first() - assertTrue(event is MemoryEvent.KnowledgeStored) - val storedEvent = event as MemoryEvent.KnowledgeStored + val storedEvent = assertIs(event) assertEquals(stored.id, storedEvent.knowledgeId) assertEquals(KnowledgeType.FROM_OUTCOME, storedEvent.knowledgeType) assertEquals("test-task", storedEvent.taskType) assertEquals(listOf("tag1", "tag2"), storedEvent.tags) - assertTrue(storedEvent.eventSource is EventSource.Agent) - assertEquals(agentId, (storedEvent.eventSource as EventSource.Agent).agentId) + val eventSource = assertIs(storedEvent.eventSource) + assertEquals(agentId, eventSource.agentId) } } @@ -581,8 +581,8 @@ class AgentMemoryServiceTest { assertTrue(recallEvent.resultsFound > 0) assertTrue(recallEvent.averageRelevance >= 0.0) assertTrue(recallEvent.topKnowledgeIds.isNotEmpty()) - assertTrue(recallEvent.eventSource is EventSource.Agent) - assertEquals(agentId, (recallEvent.eventSource as EventSource.Agent).agentId) + val eventSource = assertIs(recallEvent.eventSource) + assertEquals(agentId, eventSource.agentId) } } // ==================== Test 8: Service Method Delegation ==================== diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/memory/OutcomeMemoryRepositoryTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/memory/OutcomeMemoryRepositoryTest.kt index b14dfec9..896f914a 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/memory/OutcomeMemoryRepositoryTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/domain/memory/OutcomeMemoryRepositoryTest.kt @@ -147,8 +147,8 @@ class OutcomeMemoryRepositoryTest { assertEquals(approach, stored.approach) assertFalse(stored.success) assertEquals(1, stored.filesChanged) // partiallyChangedFiles - assertNotNull(stored.errorMessage) - assertTrue(stored.errorMessage!!.contains(errorMessage)) + val storedErrorMessage = assertNotNull(stored.errorMessage) + assertTrue(storedErrorMessage.contains(errorMessage)) } @Test diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/environment/WorkspaceStateStoreTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/environment/WorkspaceStateStoreTest.kt index ea47345a..588451e6 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/environment/WorkspaceStateStoreTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/environment/WorkspaceStateStoreTest.kt @@ -2,6 +2,7 @@ package link.socket.ampere.agents.environment import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertIs import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -204,9 +205,9 @@ class WorkspaceStateStoreTest { ) val item = state.items["task-1"]!! - assertTrue(item.status is TaskStatus.Completed) + val status = assertIs(item.status) assertEquals(1f, item.progress) - assertEquals(completedAt, (item.status as TaskStatus.Completed).completedAt) + assertEquals(completedAt, status.completedAt) } @Test @@ -238,8 +239,8 @@ class WorkspaceStateStoreTest { ) val item = state.items["task-1"]!! - assertTrue(item.status is TaskStatus.Blocked) - assertEquals("Build failed: missing dependency", (item.status as TaskStatus.Blocked).reason) + val status = assertIs(item.status) + assertEquals("Build failed: missing dependency", status.reason) } @Test diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/events/meetings/AgentMeetingsApiTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/events/meetings/AgentMeetingsApiTest.kt index a1a23fad..6c837489 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/events/meetings/AgentMeetingsApiTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/events/meetings/AgentMeetingsApiTest.kt @@ -1,3 +1,5 @@ +@file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) + package link.socket.ampere.agents.events.meetings import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/implementations/ProjectManagerAgentTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/implementations/ProjectManagerAgentTest.kt index 7c76889b..908c0d10 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/implementations/ProjectManagerAgentTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/implementations/ProjectManagerAgentTest.kt @@ -2,6 +2,8 @@ package link.socket.ampere.agents.implementations import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.test.assertIs +import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -168,11 +170,11 @@ class ProjectManagerAgentTest { val knowledge = projectAgent.extractKnowledgeFromOutcome(outcome, task, plan) - assertTrue(knowledge.approach.isNotEmpty(), "Approach should be captured") - assertTrue(knowledge.learnings.contains("succeeded"), "Learnings should mention success") - assertTrue(knowledge.learnings.contains("2"), "Learnings should mention number of issues created") - assertTrue(knowledge is Knowledge.FromOutcome, "Knowledge should be FromOutcome type") - assertTrue(knowledge.outcomeId == outcome.id, "Knowledge should reference the correct outcome ID") + val fromOutcome = assertIs(knowledge, "Knowledge should be FromOutcome type") + assertTrue(fromOutcome.approach.isNotEmpty(), "Approach should be captured") + assertTrue(fromOutcome.learnings.contains("succeeded"), "Learnings should mention success") + assertTrue(fromOutcome.learnings.contains("2"), "Learnings should mention number of issues created") + assertTrue(fromOutcome.outcomeId == outcome.id, "Knowledge should reference the correct outcome ID") } @Test @@ -204,10 +206,10 @@ class ProjectManagerAgentTest { val knowledge = projectAgent.extractKnowledgeFromOutcome(outcome, task, plan) - assertTrue(knowledge.approach.isNotEmpty(), "Approach should be captured") - assertTrue(knowledge.learnings.contains("failed"), "Learnings should mention failure") - assertTrue(knowledge.learnings.contains("rate limit"), "Learnings should mention the error") - assertTrue(knowledge is Knowledge.FromOutcome, "Knowledge should be FromOutcome type") + val fromOutcome = assertIs(knowledge, "Knowledge should be FromOutcome type") + assertTrue(fromOutcome.approach.isNotEmpty(), "Approach should be captured") + assertTrue(fromOutcome.learnings.contains("failed"), "Learnings should mention failure") + assertTrue(fromOutcome.learnings.contains("rate limit"), "Learnings should mention the error") } @Test @@ -234,15 +236,15 @@ class ProjectManagerAgentTest { val knowledge = projectAgent.extractKnowledgeFromOutcome(outcome, task, plan) - assertTrue(knowledge.approach.isNotEmpty(), "Approach should be captured") + val fromOutcome = assertIs(knowledge, "Knowledge should be FromOutcome type") + assertTrue(fromOutcome.approach.isNotEmpty(), "Approach should be captured") // Learnings should contain the outcome message or indicate success assertTrue( - knowledge.learnings.contains("SUCCEEDED") || - knowledge.learnings.contains("authentication") || - knowledge.learnings.contains("Human"), - "Learnings should capture the outcome: ${knowledge.learnings}", + fromOutcome.learnings.contains("SUCCEEDED") || + fromOutcome.learnings.contains("authentication") || + fromOutcome.learnings.contains("Human"), + "Learnings should capture the outcome: ${fromOutcome.learnings}", ) - assertTrue(knowledge is Knowledge.FromOutcome, "Knowledge should be FromOutcome type") } @Test @@ -269,9 +271,11 @@ class ProjectManagerAgentTest { val perception = projectAgent.perceiveState(currentState) // With mock reasoning, we get a "Project Manager Perception" idea - val perceptionIdea = perception.ideas.find { it.name.contains("Project Manager") } - assertTrue(perceptionIdea != null, "Should have perception idea from mock") - assertTrue(perceptionIdea!!.description.isNotEmpty(), "Idea should have description") + val perceptionIdea = assertNotNull( + perception.ideas.find { it.name.contains("Project Manager") }, + "Should have perception idea from mock", + ) + assertTrue(perceptionIdea.description.isNotEmpty(), "Idea should have description") } @Test diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/implementations/QualityAssuranceAgentTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/implementations/QualityAssuranceAgentTest.kt index 7d661ce0..1bca22d4 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/implementations/QualityAssuranceAgentTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/implementations/QualityAssuranceAgentTest.kt @@ -2,6 +2,7 @@ package link.socket.ampere.agents.implementations import kotlin.test.BeforeTest import kotlin.test.Test +import kotlin.test.assertIs import kotlin.test.assertTrue import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock @@ -70,9 +71,9 @@ class QualityAssuranceAgentTest { val knowledge = qualityAgent.extractKnowledgeFromOutcome(outcome, task, plan) - assertTrue(knowledge.approach.isNotEmpty(), "Approach should be captured") - assertTrue(knowledge.learnings.contains("Success"), "Learnings should mention success") - assertTrue(knowledge is Knowledge.FromOutcome) + val fromOutcome = assertIs(knowledge) + assertTrue(fromOutcome.approach.isNotEmpty(), "Approach should be captured") + assertTrue(fromOutcome.learnings.contains("Success"), "Learnings should mention success") } @Test diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/tools/mcp/McpToolExecutorTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/tools/mcp/McpToolExecutorTest.kt index 7fc1dc00..6ebfd629 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/tools/mcp/McpToolExecutorTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/agents/tools/mcp/McpToolExecutorTest.kt @@ -140,8 +140,8 @@ class McpToolExecutorTest { val outcome = executor.execute(tool, request) assertIs(outcome) - assertIs(outcome) - assertTrue((outcome as ExecutionOutcome.NoChanges.Failure).message.contains("not connected")) + val failure = assertIs(outcome) + assertTrue(failure.message.contains("not connected")) } /** @@ -170,8 +170,8 @@ class McpToolExecutorTest { val outcome = executor.execute(tool, request) assertIs(outcome) - assertIs(outcome) - assertTrue((outcome as ExecutionOutcome.NoChanges.Failure).message.contains("Permission denied")) + val failure = assertIs(outcome) + assertTrue(failure.message.contains("Permission denied")) } /** @@ -197,8 +197,8 @@ class McpToolExecutorTest { val outcome = executor.execute(tool, request) assertIs(outcome) - assertIs(outcome) - assertTrue((outcome as ExecutionOutcome.NoChanges.Failure).message.contains("Network timeout")) + val failure = assertIs(outcome) + assertTrue(failure.message.contains("Network timeout")) } /** @@ -225,8 +225,8 @@ class McpToolExecutorTest { val outcome = executor.execute(tool, request) assertIs(outcome) - assertIs(outcome) - assertTrue((outcome as ExecutionOutcome.NoChanges.Failure).message.contains("no longer active")) + val failure = assertIs(outcome) + assertTrue(failure.message.contains("no longer active")) } /** @@ -259,8 +259,8 @@ class McpToolExecutorTest { val outcome = executor.execute(tool, request) assertIs(outcome) - assertIs(outcome) - val message = (outcome as ExecutionOutcome.NoChanges.Success).message + val success = assertIs(outcome) + val message = success.message assertTrue(message.contains("Line 1")) assertTrue(message.contains("Line 2")) } diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/integrations/issues/github/GitHubCliProviderTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/integrations/issues/github/GitHubCliProviderTest.kt index ba3a4e4b..7132255b 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/integrations/issues/github/GitHubCliProviderTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/integrations/issues/github/GitHubCliProviderTest.kt @@ -33,9 +33,9 @@ class GitHubCliProviderTest { @Test fun `provider implements IssueTrackerProvider interface`() { - val provider = GitHubCliProvider() + val provider: IssueTrackerProvider = GitHubCliProvider() - assertTrue(provider is IssueTrackerProvider) + assertEquals("github-cli", provider.providerId) } @Test diff --git a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/llm/CustomLlmProviderIntegrationTest.kt b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/llm/CustomLlmProviderIntegrationTest.kt index b4ad5f56..671bd6ee 100644 --- a/ampere-core/src/jvmTest/kotlin/link/socket/ampere/llm/CustomLlmProviderIntegrationTest.kt +++ b/ampere-core/src/jvmTest/kotlin/link/socket/ampere/llm/CustomLlmProviderIntegrationTest.kt @@ -3,6 +3,7 @@ package link.socket.ampere.llm import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals +import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlinx.coroutines.test.runTest import link.socket.ampere.agents.config.AgentConfiguration @@ -68,8 +69,9 @@ class CustomLlmProviderIntegrationTest { assertTrue(providerCalled, "Custom provider should have been called") assertEquals("Mock response from custom provider", response) - assertContains(receivedPrompt!!, "You are a calculator.") - assertContains(receivedPrompt!!, "What is 2 + 2?") + val prompt = assertNotNull(receivedPrompt) + assertContains(prompt, "You are a calculator.") + assertContains(prompt, "What is 2 + 2?") } /** @@ -99,8 +101,9 @@ class CustomLlmProviderIntegrationTest { ) // Verify format: System: ... \n\n User: ... - assertTrue(receivedPrompt!!.startsWith("System: Be helpful")) - assertTrue(receivedPrompt!!.contains("\n\nUser: Hello world")) + val prompt = assertNotNull(receivedPrompt) + assertTrue(prompt.startsWith("System: Be helpful")) + assertTrue(prompt.contains("\n\nUser: Hello world")) } /**