Communitygithub.com

kotlin-testing

Kotlin のテストパターン集:Kotest、MockK、コルーチンテスト、プロパティベーステスト、Koverカバレッジ。TDD手法に従い、慣用的なKotlinプラクティスを適用します。KotestやMockKを使ったKotlinテスト作成、コルーチンテスト、カバレッジ確認時に使用します。

kotlin-testing とは?

このスキルは、Kotlinプロジェクトにおける信頼性が高く保守しやすいテストを記述するための包括的なテストパターン集です。Kotestの各スペックスタイル(StringSpec、FunSpec、BehaviorSpec、DescribeSpec)、MockKを使用した依存関係のモック化、コルーチンやFlowのテスト、プロパティベーステスト、Koverによるコードカバレッジの確認方法を詳しく解説します。TDD(RED/GREEN/REFACTOR)のワークフローに沿った実践的な例が豊富に含まれており、Claude Code、Cursor、CodexなどのAIエージェントと連携して、Kotlinコードの品質向上とテスト駆動開発の効率化を図ることができます。特に、EmailValidatorを使った段階的なTDDチュートリアルや、各テストシナリオにおける具体的なコード例が、開発者にとって非常に有用です。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/kotlin-testing

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Kotlin テストパターン

Kotest と MockK を使用し、TDD手法に従って信頼性が高く保守しやすいテストを記述するための包括的なKotlinテストパターン集です。

使用タイミング

  • 新しいKotlin関数やクラスを作成するとき
  • 既存のKotlinコードにテストカバレッジを追加するとき
  • プロパティベースのテストを実装するとき
  • KotlinプロジェクトでTDDワークフローに従うとき
  • Koverを設定してコードカバレッジを確認するとき

仕組み

  1. テスト対象を特定する — テストする関数、クラス、モジュールを見つける
  2. Kotestスペックを書く — テスト範囲に合ったスペックスタイル(StringSpec、FunSpec、BehaviorSpec)を選択する
  3. 依存関係をモックする — MockKを使用してテスト対象を分離する
  4. テストを実行する(RED) — 期待通りのエラーでテストが失敗することを確認する
  5. コードを実装する(GREEN) — テストに合格する最小限のコードを書く
  6. リファクタリングする — テストが緑のまま実装を改善する
  7. カバレッジを確認する — ./gradlew koverHtmlReport を実行し、80%以上のカバレッジを確認する

例

以下のセクションには、各テストパターンの詳細で実行可能な例が含まれています。

クイックリファレンス

  • Kotestスペック — Kotest Spec Styles のStringSpec、FunSpec、BehaviorSpec、DescribeSpecの例
  • モッキング — MockK のMockKセットアップ、コルーチンモッキング、引数キャプチャ
  • TDDチュートリアル — Kotlin向けTDDワークフロー のEmailValidatorを使ったRED/GREEN/REFACTORの完全なサイクル
  • カバレッジ — Kover Coverage のKover設定とコマンド
  • Ktorテスト — Ktor testApplication Testing のtestApplicationセットアップ

Kotlin向けTDDワークフロー

RED-GREEN-REFACTORサイクル

RED     -> 最初に失敗するテストを書く
GREEN   -> テストに合格する最小限のコードを書く
REFACTOR -> テストが緑のままコードを改善する
REPEAT  -> 次の要件に進む

KotlinでのステップバイステップTDD

// ステップ 1: インターフェース/シグネチャを定義する
// EmailValidator.kt
package com.example.validator

fun validateEmail(email: String): Result<String> {
    TODO("not implemented")
}

// ステップ 2: 失敗するテストを書く(RED)
// EmailValidatorTest.kt
package com.example.validator

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.result.shouldBeFailure
import io.kotest.matchers.result.shouldBeSuccess

class EmailValidatorTest : StringSpec({
    "有効なメールアドレスは成功を返す" {
        validateEmail("[email protected]").shouldBeSuccess("[email protected]")
    }

    "空のメールアドレスは失敗を返す" {
        validateEmail("").shouldBeFailure()
    }

    "@を含まないメールアドレスは失敗を返す" {
        validateEmail("userexample.com").shouldBeFailure()
    }
})

// ステップ 3: テストを実行する - 失敗を確認する
// $ ./gradlew test
// EmailValidatorTest > valid email returns success FAILED
//   kotlin.NotImplementedError: An operation is not implemented

// ステップ 4: 最小限のコードを実装する(GREEN)
fun validateEmail(email: String): Result<String> {
    if (email.isBlank()) return Result.failure(IllegalArgumentException("メールアドレスは空にできません"))
    if ('@' !in email) return Result.failure(IllegalArgumentException("メールアドレスには@が必要です"))
    val regex = Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
    if (!regex.matches(email)) return Result.failure(IllegalArgumentException("メールアドレスの形式が無効です"))
    return Result.success(email)
}

// ステップ 5: テストを実行する - 成功を確認する
// $ ./gradlew test
// EmailValidatorTest > valid email returns success PASSED
// EmailValidatorTest > empty email returns failure PASSED
// EmailValidatorTest > email without @ returns failure PASSED

// ステップ 6: 必要に応じてリファクタリングし、テストがまだ成功することを確認する

Kotest Spec Styles

StringSpec(最もシンプル)

class CalculatorTest : StringSpec({
    "2つの正の数を加算する" {
        Calculator.add(2, 3) shouldBe 5
    }

    "負の数を加算する" {
        Calculator.add(-1, -2) shouldBe -3
    }

    "ゼロを加算する" {
        Calculator.add(0, 5) shouldBe 5
    }
})

FunSpec(JUnit風)

class UserServiceTest : FunSpec({
    val repository = mockk<UserRepository>()
    val service = UserService(repository)

    test("getUserはユーザーが見つかった場合にユーザーを返す") {
        val expected = User(id = "1", name = "Alice")
        coEvery { repository.findById("1") } returns expected

        val result = service.getUser("1")

        result shouldBe expected
    }

    test("getUserは見つからない場合に例外をスローする") {
        coEvery { repository.findById("999") } returns null

        shouldThrow<UserNotFoundException> {
            service.getUser("999")
        }
    }
})

BehaviorSpec(BDDスタイル)

class OrderServiceTest : BehaviorSpec({
    val repository = mockk<OrderRepository>()
    val paymentService = mockk<PaymentService>()
    val service = OrderService(repository, paymentService)

    Given("有効な注文リクエスト") {
        val request = CreateOrderRequest(
            userId = "user-1",
            items = listOf(OrderItem("product-1", quantity = 2)),
        )

        When("注文が行われる") {
            coEvery { paymentService.charge(any()) } returns PaymentResult.Success
            coEvery { repository.save(any()) } answers { firstArg() }

            val result = service.placeOrder(request)

            Then("確定された注文を返す") {
                result.status shouldBe OrderStatus.CONFIRMED
            }

            Then("支払いが請求される") {
                coVerify(exactly = 1) { paymentService.charge(any()) }
            }
        }

        When("支払いが失敗する") {
            coEvery { paymentService.charge(any()) } returns PaymentResult.Declined

            Then("PaymentExceptionをスローする") {
                shouldThrow<PaymentException> {
                    service.placeOrder(request)
                }
            }
        }
    }
})

DescribeSpec(RSpecスタイル)

class UserValidatorTest : DescribeSpec({
    describe("validateUser") {
        val validator = UserValidator()

        context("有効な入力の場合") {
            it("通常のユーザーを受け入れる") {
                val user = CreateUserRequest("Alice", "[email protected]")
                validator.validate(user).shouldBeValid()
            }
        }

        context("無効な名前の場合") {
            it("空白の名前を拒否する") {
                val user = CreateUserRequest("", "[email protected]")
                validator.validate(user).shouldBeInvalid()
            }

            it("最大長を超える名前を拒否する") {
                val user = CreateUserRequest("A".repeat(256), "[email protected]")
                validator.validate(user).shouldBeInvalid()
            }
        }
    }
})

Kotest Matchers

コアMatcher

import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.*
import io.kotest.matchers.collections.*
import io.kotest.matchers.nulls.*

// 等価性
result shouldBe expected
result shouldNotBe unexpected

// 文字列
name shouldStartWith "Al"
name shouldEndWith "ice"
name shouldContain "lic"
name shouldMatch Regex("[A-Z][a-z]+")
name.shouldBeBlank()

// コレクション
list shouldContain "item"
list shouldHaveSize 3
list.shouldBeSorted()
list.shouldContainAll("a", "b", "c")
list.shouldBeEmpty()

// Null
result.shouldNotBeNull()
result.shouldBeNull()

// 型
result.shouldBeInstanceOf<User>()

// 数値
count shouldBeGreaterThan 0
price shouldBeInRange 1.0..100.0

// 例外
shouldThrow<IllegalArgumentException> {
    validateAge(-1)
}.message shouldBe "年齢は正の値である必要があります"

shouldNotThrow<Exception> {
    validateAge(25)
}

カスタムMatcher

fun beActiveUser() = object : Matcher<User> {
    override fun test(value: User) = MatcherResult(
        value.isActive && value.lastLogin != null,
        { "ユーザー ${value.id} はアクティブで最終ログイン日時が必要です" },
        { "ユーザー ${value.id} はアクティブであってはいけません" },
    )
}

// 使用法
user should beActiveUser()

MockK

基本モッキング

class UserServiceTest : FunSpec({
    val repository = mockk<UserRepository>()
    val logger = mockk<Logger>(relaxed = true) // Relaxed: デフォルト値を返す
    val service = UserService(repository, logger)

    beforeTest {
        clearMocks(repository, logger)
    }

    test("findUserはリポジトリに委譲する") {
        val expected = User(id = "1", name = "Alice")
        every { repository.findById("1") } returns expected

        val result = service.findUser("1")

        result shouldBe expected
        verify(exactly = 1) { repository.findById("1") }
    }

    test("findUserは不明なIDに対してnullを返す") {
        every { repository.findById(any()) } returns null

        val result = service.findUser("unknown")

        result.shouldBeNull()
    }
})

コルーチンモッキング

class AsyncUserServiceTest : FunSpec({
    val repository = mockk<UserRepository>()
    val service = UserService(repository)

    test("getUserのサスペンド関数") {
        coEvery { repository.findById("1") } returns User(id = "1", name = "Alice")

        val result = service.getUser("1")

        result.name shouldBe "Alice"
        coVerify { repository.findById("1") }
    }

    test("getUserに遅延がある場合") {
        coEvery { repository.findById("1") } coAnswers {
            delay(100) // 非同期処理をシミュレート
            User(id = "1", name = "Alice")
        }

        val result = service.getUser("1")
        result.name shouldBe "Alice"
    }
})

引数キャプチャ

test("saveはユーザー引数をキャプチャする") {
    val slot = slot<User>()
    coEvery { repository.save(capture(slot)) } returns Unit

    service.createUser(CreateUserRequest("Alice", "[email protected]"))

    slot.captured.name shouldBe "Alice"
    slot.captured.email shouldBe "[email protected]"
    slot.captured.id.shouldNotBeNull()
}

Spyと部分モッキング

test("実際のオブジェクトをスパイする") {
    val realService = UserService(repository)
    val spy = spyk(realService)

    every { spy.generateId() } returns "fixed-id"

    spy.createUser(request)

    verify { spy.generateId() } // オーバーライドされた
    // 他のメソッドは実際の実装を使用
}

コルーチンテスト

サスペンド関数のrunTest

import kotlinx.coroutines.test.runTest

class CoroutineServiceTest : FunSpec({
    test("同時フェッチが一緒に完了する") {
        runTest {
            val service = DataService(testScope = this)

            val result = service.fetchAllData()

            result.users.shouldNotBeEmpty()
            result.products.shouldNotBeEmpty()
        }
    }

    test("遅延後にタイムアウトする") {
        runTest {
            val service = SlowService()

            shouldThrow<TimeoutCancellationException> {
                withTimeout(100) {
                    service.slowOperation() // 100ms以上かかる
                }
            }
        }
    }
})

Flowのテスト

import io.kotest.matchers.collections.shouldContainInOrder
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest

class FlowServiceTest : FunSpec({
    test("observeUsersは更新を発行する") {
        runTest {
            val service = UserFlowService()

            val emissions = service.observeUsers()
                .take(3)
                .toList()

            emissions shouldHaveSize 3
            emissions.last().shouldNotBeEmpty()
        }
    }

    test("searchUsersは入力をデバウンスする") {
        runTest {
            val service = SearchService()
            val queries = MutableSharedFlow<String>()

            val results = mutableListOf<List<User>>()
            val job = launch {
                service.searchUsers(queries).collect { results.add(it) }
            }

            queries.emit("a")
            queries.emit("ab")
            queries.emit("abc") // これだけが検索をトリガーする
            advanceTimeBy(500)

            results shouldHaveSize 1
            job.cancel()
        }
    }
})

TestDispatcher

import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.advanceUntilIdle

class DispatcherTest : FunSpec({
    test("制御された実行のためにテストディスパッチャを使用する") {
        val dispatcher = StandardTestDispatcher()

        runTest(dispatcher) {
            var completed = false

            launch {
                delay(1000)
                completed = true
            }

            completed shouldBe false
            advanceTimeBy(1000)
            completed shouldBe true
        }
    }
})

プロパティベーステスト

Kotestプロパティテスト

import io.kotest.core.spec.style.FunSpec
import io.kotest.property.Arb
import io.kotest.property.arbitrary.*
import io.kotest.property.forAll
import io.kotest.property.checkAll
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString

// 注: 以下のシリアル化ラウンドトリップテストでは、Userデータクラスに
// @Serializable(kotlinx.serializationから)のアノテーションが必要です。

class PropertyTest : FunSpec({
    test("文字列の反転は対合である") {
        forAll<String> { s ->
            s.reversed().reversed() == s
        }
    }

    test("リストのソートは冪等である") {
        forAll(Arb.list(Arb.int())) { list ->
            list.sorted() == list.sorted().sorted()
        }
    }

    test("シリアル化ラウンドトリップでデータが保持される") {
        checkAll(Arb.bind(Arb.string(1..50), Arb.string(5..100)) { name, email ->
            User(name = name, email = "$email@test.com")
        }) { user ->
            val json = Json.encodeToString(user)
            val decoded = Json.decodeFromString<User>(json)
            decoded shouldBe user
        }
    }
})

カスタムジェネレータ

class CustomGeneratorTest : FunSpec({
    test("順序付きのユーザーデータを生成する") {
        val userArb = Arb.bind(
            Arb.string(1..50),
            Arb.email(),
            Arb.int(18..120),
        ) { name, email, age ->
            User(name = name, email = email, age = age)
        }

        checkAll(userArb) { user ->
            user.age shouldBeGreaterThanOrEqual 18
            user.email shouldContain "@"
        }
    }

    test("ネストされたデータ構造を生成する") {
        data class Order(val id: String, val items: List<OrderItem>)
        val orderArb = Arb.bind(
            Arb.string(10..20),
            Arb.list(Arb.bind(Arb.string(), Arb.int(1..100)) { name, qty ->
                OrderItem(name, qty)
            }, 1..10),
        ) { id, items -> Order(id, items) }

        checkAll(orderArb) { order ->
            order.id.length shouldBeGreaterThanOrEqual 10
            order.items.shouldNotBeEmpty()
        }
    }
})

fun Arb.Companion.email(): Arb<String> = arbitrary {
    "${randomString()}@${randomString()}.com"
}

fun randomString(length: Int = 10) = (1..length)
    .map { ('a'..'z').random() }
    .joinToString("")

Koverカバレッジ

Gradle設定(build.gradle.kts)

plugins {
    id("org.jetbrains.kotlin.jvm") version "1.9.0"
    id("org.jetbrains.kotlin.plugin.serialization") version "1.9.0"
    id("org.jetbrains.kotlinx.kover") version "0.8.0"
}

// Koverの設定
kover {
    // カバレッジルール:行カバレッジ80%
    coverageRules {
        rule {
            min = 0.8
        }
    }

    // ジェネレーションから特定のクラスを除外
    excludes {
        classes(
            "com.example.generated.*",
            "com.example.MainKt",
        )
    }
}

dependencies {
    testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
    testImplementation("io.kotest:kotest-assertions-core:5.9.1")
    testImplementation("io.kotest:kotest-property:5.9.1")
    testImplementation("io.mockk:mockk:1.13.12")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
}

コマンド

# テストを実行し、HTMLレポートを生成する
./gradlew koverHtmlReport

# 検証ビルド中にXMLレポートを生成する
./gradlew koverXmlReport

# テストカバレッジルールを検証する
./gradlew koverVerify

# Koverレポートの出力場所:
# build/reports/kover/html/index.html
# build/reports/kover/xml/report.xml

重要な注意事項:

  • カバレッジルールは koverVerify タスクで検証されます。検証に失敗した場合、ビルドは失敗します。
  • 除外は kover.excludes ブロックにクラスまたはパターンを追加することで設定します。
  • 変更を加えた後は、常に ./gradlew koverHtmlReport を実行して更新されたレポートを確認してください。

Ktor testApplicationテスト

基本的なKtorテスト

import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.testing.*

class UserRoutesTest : FunSpec({
    test("GET /users が200を返す") {
        testApplication {
            application {
                configureRoutes()
            }

            val response = client.get("/users")

            response.status shouldBe HttpStatusCode.OK
        }
    }

    test("POST /users が201を返す") {
        testApplication {
            application {
                configureRoutes()
            }

            val response = client.post("/users") {
                contentType(ContentType.Application.Json)
                setBody("""{"name":"Alice","email":"[email protected]"}""")
            }

            response.status shouldBe HttpStatusCode.Created
        }
    }

    test("GET /users/:id が存在しないIDに対して404を返す") {
        testApplication {
            application {
                configureRoutes()
            }

            val response = client.get("/users/999")

            response.status shouldBe HttpStatusCode.NotFound
        }
    }
})

モックリポジトリを使ったKtorテスト

class UserRoutesMockTest : FunSpec({
    test("モックリポジトリを使ってユーザーを取得する") {
        val repository = mockk<UserRepository>()
        every { repository.findAll() } returns listOf(
            User(id = "1", name = "Alice"),
            User(id = "2", name = "Bob"),
        )

        testApplication {
            application {
                configureRoutes(repository)
            }

            val response = client.get("/users")
            val body = response.bodyAsText()

            response.status shouldBe HttpStatusCode.OK
            body shouldContain "Alice"
            body shouldContain "Bob"
        }
    }
})

認証が必要なエンドポイントのテスト

class AuthenticatedRoutesTest : FunSpec({
    val jwtSecret = "test-secret"

    test("認証なしのリクエストは401を返す") {
        testApplication {
            application {
                configureAuthRoutes(jwtSecret)
            }

            val response = client.get("/protected")

            response.status shouldBe HttpStatusCode.Unauthorized
        }
    }

    test("有効なJWTトークンで認証する") {
        testApplication {
            application {
                configureAuthRoutes(jwtSecret)
            }

            val token = generateTestToken("user-1", jwtSecret)

            val response = client.get("/protected") {
                bearerAuth(token)
            }

            response.status shouldBe HttpStatusCode.OK
        }
    }
})

ベストプラクティスと規則

命名規則

テスト名は、テスト対象となる動作を記述する必要があります。

✅ "有効なメールアドレスは検証に合格する"
✅ "空の文字列はIllegalArgumentExceptionをスローする"
❌ "test1"
❌ "メールのテスト"

テスト構成

Kotlinプロジェクトの標準的なテスト構成:

src/
  main/
    kotlin/
      com/example/
        EmailValidator.kt
  test/
    kotlin/
      com/example/
        EmailValidatorTest.kt

コード品質の列

品質説明
読みやすさスペックスタイルと記述的なテスト名を使用する。テストは仕様書として読めるべき
分離MockKを使用して依存関係をモックし、テスト対象を分離する
速度テストが高速(ミリ秒単位)であることを確認する。低速なテストは統合テストに移動する
決定性テスト毎にテストデータをリセットする。テスト間の共有状態を避ける
カバレッジコードの80%以上をカバーすることを目指し、重要な分岐をカバーする

よくある落とし穴

  1. テストを実行し忘れる — 変更後は常に ./gradlew test を実行する
  2. 偽陽性テスト — アサーションが期待通りに動作することを確認する(例: shouldBe が実際に呼び出されているか)
  3. 脆弱なテスト — 実装の詳細ではなく、動作に対してテストする
  4. テストが遅い — ネットワーク/ファイルI/Oを除く高速なテストに留める
  5. MockKのrelaxedを見逃す — 未定義の動作に対して予期しないデフォルトが返される可能性があるため、relaxed = trueを使用する際は注意する

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

関連スキル