[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DAGV32XK6BADM3PCRYHAJG":3,"skill-same:01M6DAGV32XK6BADM3PCRYHAJG":141},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":27,"linkOnly":28,"markdownContent":29,"markdownBody":30,"frontMatter":31,"refName":33,"commitSha":34,"skillDirectory":35,"forksCount":14,"repositoryHtmlUrl":36,"repositoryHomepageUrl":6,"ownerHtmlUrl":37,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":38,"install":39,"resources":52},"01M6DAGV32XK6BADM3PCRYHAJG","kotlin-patterns",null,"Idiomatic Kotlin patterns, best practices, and conventions for building robust, efficient, and maintainable Kotlin applications with coroutines, null safety, and DSL builders.","Prekzursil\u002Fagent-skills-toolchain","Prekzursil","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F54636077?v=4","01M6DACZ1PCQHD99SCR1YXG28B","Andrei Mihai Visalon","01M6DACZ670WZF535V3WYYSS4Z",0,"MIT","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Flibrary\u002Fskills\u002Fkotlin-patterns\u002FSKILL.md",[],[19,20,21,22,23,24,25,26],"kotlin patterns","kotlin","patterns","best practices","Safety","applications","coroutines","conventions","2026-11-10 10:12:30",false,"---\nname: kotlin-patterns\ndescription: Idiomatic Kotlin patterns, best practices, and conventions for building robust, efficient, and maintainable Kotlin applications with coroutines, null safety, and DSL builders.\norigin: ECC\n---\n\n# Kotlin Development Patterns\n\nIdiomatic Kotlin patterns and best practices for building robust, efficient, and maintainable applications.\n\n## When to Use\n\n- Writing new Kotlin code\n- Reviewing Kotlin code\n- Refactoring existing Kotlin code\n- Designing Kotlin modules or libraries\n- Configuring Gradle Kotlin DSL builds\n\n## How It Works\n\nThis skill enforces idiomatic Kotlin conventions across seven key areas: null safety using the type system and safe-call operators, immutability via `val` and `copy()` on data classes, sealed classes and interfaces for exhaustive type hierarchies, structured concurrency with coroutines and `Flow`, extension functions for adding behaviour without inheritance, type-safe DSL builders using `@DslMarker` and lambda receivers, and Gradle Kotlin DSL for build configuration.\n\n## Examples\n\n**Null safety with Elvis operator:**\n```kotlin\nfun getUserEmail(userId: String): String {\n    val user = userRepository.findById(userId)\n    return user?.email ?: \"unknown@example.com\"\n}\n```\n\n**Sealed class for exhaustive results:**\n```kotlin\nsealed class Result\u003Cout T> {\n    data class Success\u003CT>(val data: T) : Result\u003CT>()\n    data class Failure(val error: AppError) : Result\u003CNothing>()\n    data object Loading : Result\u003CNothing>()\n}\n```\n\n**Structured concurrency with async\u002Fawait:**\n```kotlin\nsuspend fun fetchUserWithPosts(userId: String): UserProfile =\n    coroutineScope {\n        val user = async { userService.getUser(userId) }\n        val posts = async { postService.getUserPosts(userId) }\n        UserProfile(user = user.await(), posts = posts.await())\n    }\n```\n\n## Core Principles\n\n### 1. Null Safety\n\nKotlin's type system distinguishes nullable and non-nullable types. Leverage it fully.\n\n```kotlin\n\u002F\u002F Good: Use non-nullable types by default\nfun getUser(id: String): User {\n    return userRepository.findById(id)\n        ?: throw UserNotFoundException(\"User $id not found\")\n}\n\n\u002F\u002F Good: Safe calls and Elvis operator\nfun getUserEmail(userId: String): String {\n    val user = userRepository.findById(userId)\n    return user?.email ?: \"unknown@example.com\"\n}\n\n\u002F\u002F Bad: Force-unwrapping nullable types\nfun getUserEmail(userId: String): String {\n    val user = userRepository.findById(userId)\n    return user!!.email \u002F\u002F Throws NPE if null\n}\n```\n\n### 2. Immutability by Default\n\nPrefer `val` over `var`, immutable collections over mutable ones.\n\n```kotlin\n\u002F\u002F Good: Immutable data\ndata class User(\n    val id: String,\n    val name: String,\n    val email: String,\n)\n\n\u002F\u002F Good: Transform with copy()\nfun updateEmail(user: User, newEmail: String): User =\n    user.copy(email = newEmail)\n\n\u002F\u002F Good: Immutable collections\nval users: List\u003CUser> = listOf(user1, user2)\nval filtered = users.filter { it.email.isNotBlank() }\n\n\u002F\u002F Bad: Mutable state\nvar currentUser: User? = null \u002F\u002F Avoid mutable global state\nval mutableUsers = mutableListOf\u003CUser>() \u002F\u002F Avoid unless truly needed\n```\n\n### 3. Expression Bodies and Single-Expression Functions\n\nUse expression bodies for concise, readable functions.\n\n```kotlin\n\u002F\u002F Good: Expression body\nfun isAdult(age: Int): Boolean = age >= 18\n\nfun formatFullName(first: String, last: String): String =\n    \"$first $last\".trim()\n\nfun User.displayName(): String =\n    name.ifBlank { email.substringBefore('@') }\n\n\u002F\u002F Good: When as expression\nfun statusMessage(code: Int): String = when (code) {\n    200 -> \"OK\"\n    404 -> \"Not Found\"\n    500 -> \"Internal Server Error\"\n    else -> \"Unknown status: $code\"\n}\n\n\u002F\u002F Bad: Unnecessary block body\nfun isAdult(age: Int): Boolean {\n    return age >= 18\n}\n```\n\n### 4. Data Classes for Value Objects\n\nUse data classes for types that primarily hold data.\n\n```kotlin\n\u002F\u002F Good: Data class with copy, equals, hashCode, toString\ndata class CreateUserRequest(\n    val name: String,\n    val email: String,\n    val role: Role = Role.USER,\n)\n\n\u002F\u002F Good: Value class for type safety (zero overhead at runtime)\n@JvmInline\nvalue class UserId(val value: String) {\n    init {\n        require(value.isNotBlank()) { \"UserId cannot be blank\" }\n    }\n}\n\n@JvmInline\nvalue class Email(val value: String) {\n    init {\n        require('@' in value) { \"Invalid email: $value\" }\n    }\n}\n\nfun getUser(id: UserId): User = userRepository.findById(id)\n```\n\n## Sealed Classes and Interfaces\n\n### Modeling Restricted Hierarchies\n\n```kotlin\n\u002F\u002F Good: Sealed class for exhaustive when\nsealed class Result\u003Cout T> {\n    data class Success\u003CT>(val data: T) : Result\u003CT>()\n    data class Failure(val error: AppError) : Result\u003CNothing>()\n    data object Loading : Result\u003CNothing>()\n}\n\nfun \u003CT> Result\u003CT>.getOrNull(): T? = when (this) {\n    is Result.Success -> data\n    is Result.Failure -> null\n    is Result.Loading -> null\n}\n\nfun \u003CT> Result\u003CT>.getOrThrow(): T = when (this) {\n    is Result.Success -> data\n    is Result.Failure -> throw error.toException()\n    is Result.Loading -> throw IllegalStateException(\"Still loading\")\n}\n```\n\n### Sealed Interfaces for API Responses\n\n```kotlin\nsealed interface ApiError {\n    val message: String\n\n    data class NotFound(override val message: String) : ApiError\n    data class Unauthorized(override val message: String) : ApiError\n    data class Validation(\n        override val message: String,\n        val field: String,\n    ) : ApiError\n    data class Internal(\n        override val message: String,\n        val cause: Throwable? = null,\n    ) : ApiError\n}\n\nfun ApiError.toStatusCode(): Int = when (this) {\n    is ApiError.NotFound -> 404\n    is ApiError.Unauthorized -> 401\n    is ApiError.Validation -> 422\n    is ApiError.Internal -> 500\n}\n```\n\n## Scope Functions\n\n### When to Use Each\n\n```kotlin\n\u002F\u002F let: Transform nullable or scoped result\nval length: Int? = name?.let { it.trim().length }\n\n\u002F\u002F apply: Configure an object (returns the object)\nval user = User().apply {\n    name = \"Alice\"\n    email = \"alice@example.com\"\n}\n\n\u002F\u002F also: Side effects (returns the object)\nval user = createUser(request).also { logger.info(\"Created user: ${it.id}\") }\n\n\u002F\u002F run: Execute a block with receiver (returns result)\nval result = connection.run {\n    prepareStatement(sql)\n    executeQuery()\n}\n\n\u002F\u002F with: Non-extension form of run\nval csv = with(StringBuilder()) {\n    appendLine(\"name,email\")\n    users.forEach { appendLine(\"${it.name},${it.email}\") }\n    toString()\n}\n```\n\n### Anti-Patterns\n\n```kotlin\n\u002F\u002F Bad: Nesting scope functions\nuser?.let { u ->\n    u.address?.let { addr ->\n        addr.city?.let { city ->\n            println(city) \u002F\u002F Hard to read\n        }\n    }\n}\n\n\u002F\u002F Good: Chain safe calls instead\nval city = user?.address?.city\ncity?.let { println(it) }\n```\n\n## Extension Functions\n\n### Adding Functionality Without Inheritance\n\n```kotlin\n\u002F\u002F Good: Domain-specific extensions\nfun String.toSlug(): String =\n    lowercase()\n        .replace(Regex(\"[^a-z0-9\\\\s-]\"), \"\")\n        .replace(Regex(\"\\\\s+\"), \"-\")\n        .trim('-')\n\nfun Instant.toLocalDate(zone: ZoneId = ZoneId.systemDefault()): LocalDate =\n    atZone(zone).toLocalDate()\n\n\u002F\u002F Good: Collection extensions\nfun \u003CT> List\u003CT>.second(): T = this[1]\n\nfun \u003CT> List\u003CT>.secondOrNull(): T? = getOrNull(1)\n\n\u002F\u002F Good: Scoped extensions (not polluting global namespace)\nclass UserService {\n    private fun User.isActive(): Boolean =\n        status == Status.ACTIVE && lastLogin.isAfter(Instant.now().minus(30, ChronoUnit.DAYS))\n\n    fun getActiveUsers(): List\u003CUser> = userRepository.findAll().filter { it.isActive() }\n}\n```\n\n## Coroutines\n\n### Structured Concurrency\n\n```kotlin\n\u002F\u002F Good: Structured concurrency with coroutineScope\nsuspend fun fetchUserWithPosts(userId: String): UserProfile =\n    coroutineScope {\n        val userDeferred = async { userService.getUser(userId) }\n        val postsDeferred = async { postService.getUserPosts(userId) }\n\n        UserProfile(\n            user = userDeferred.await(),\n            posts = postsDeferred.await(),\n        )\n    }\n\n\u002F\u002F Good: supervisorScope when children can fail independently\nsuspend fun fetchDashboard(userId: String): Dashboard =\n    supervisorScope {\n        val user = async { userService.getUser(userId) }\n        val notifications = async { notificationService.getRecent(userId) }\n        val recommendations = async { recommendationService.getFor(userId) }\n\n        Dashboard(\n            user = user.await(),\n            notifications = try {\n                notifications.await()\n            } catch (e: CancellationException) {\n                throw e\n            } catch (e: Exception) {\n                emptyList()\n            },\n            recommendations = try {\n                recommendations.await()\n            } catch (e: CancellationException) {\n                throw e\n            } catch (e: Exception) {\n                emptyList()\n            },\n        )\n    }\n```\n\n### Flow for Reactive Streams\n\n```kotlin\n\u002F\u002F Good: Cold flow with proper error handling\nfun observeUsers(): Flow\u003CList\u003CUser>> = flow {\n    while (currentCoroutineContext().isActive) {\n        val users = userRepository.findAll()\n        emit(users)\n        delay(5.seconds)\n    }\n}.catch { e ->\n    logger.error(\"Error observing users\", e)\n    emit(emptyList())\n}\n\n\u002F\u002F Good: Flow operators\nfun searchUsers(query: Flow\u003CString>): Flow\u003CList\u003CUser>> =\n    query\n        .debounce(300.milliseconds)\n        .distinctUntilChanged()\n        .filter { it.length >= 2 }\n        .mapLatest { q -> userRepository.search(q) }\n        .catch { emit(emptyList()) }\n```\n\n### Cancellation and Cleanup\n\n```kotlin\n\u002F\u002F Good: Respect cancellation\nsuspend fun processItems(items: List\u003CItem>) {\n    items.forEach { item ->\n        ensureActive() \u002F\u002F Check cancellation before expensive work\n        processItem(item)\n    }\n}\n\n\u002F\u002F Good: Cleanup with try\u002Ffinally\nsuspend fun acquireAndProcess() {\n    val resource = acquireResource()\n    try {\n        resource.process()\n    } finally {\n        withContext(NonCancellable) {\n            resource.release() \u002F\u002F Always release, even on cancellation\n        }\n    }\n}\n```\n\n## Delegation\n\n### Property Delegation\n\n```kotlin\n\u002F\u002F Lazy initialization\nval expensiveData: List\u003CUser> by lazy {\n    userRepository.findAll()\n}\n\n\u002F\u002F Observable property\nvar name: String by Delegates.observable(\"initial\") { _, old, new ->\n    logger.info(\"Name changed from '$old' to '$new'\")\n}\n\n\u002F\u002F Map-backed properties\nclass Config(private val map: Map\u003CString, Any?>) {\n    val host: String by map\n    val port: Int by map\n    val debug: Boolean by map\n}\n\nval config = Config(mapOf(\"host\" to \"localhost\", \"port\" to 8080, \"debug\" to true))\n```\n\n### Interface Delegation\n\n```kotlin\n\u002F\u002F Good: Delegate interface implementation\nclass LoggingUserRepository(\n    private val delegate: UserRepository,\n    private val logger: Logger,\n) : UserRepository by delegate {\n    \u002F\u002F Only override what you need to add logging to\n    override suspend fun findById(id: String): User? {\n        logger.info(\"Finding user by id: $id\")\n        return delegate.findById(id).also {\n            logger.info(\"Found user: ${it?.name ?: \"null\"}\")\n        }\n    }\n}\n```\n\n## DSL Builders\n\n### Type-Safe Builders\n\n```kotlin\n\u002F\u002F Good: DSL with @DslMarker\n@DslMarker\nannotation class HtmlDsl\n\n@HtmlDsl\nclass HTML {\n    private val children = mutableListOf\u003CElement>()\n\n    fun head(init: Head.() -> Unit) {\n        children += Head().apply(init)\n    }\n\n    fun body(init: Body.() -> Unit) {\n        children += Body().apply(init)\n    }\n\n    override fun toString(): String = children.joinToString(\"\\n\")\n}\n\nfun html(init: HTML.() -> Unit): HTML = HTML().apply(init)\n\n\u002F\u002F Usage\nval page = html {\n    head { title(\"My Page\") }\n    body {\n        h1(\"Welcome\")\n        p(\"Hello, World!\")\n    }\n}\n```\n\n### Configuration DSL\n\n```kotlin\ndata class ServerConfig(\n    val host: String = \"0.0.0.0\",\n    val port: Int = 8080,\n    val ssl: SslConfig? = null,\n    val database: DatabaseConfig? = null,\n)\n\ndata class SslConfig(val certPath: String, val keyPath: String)\ndata class DatabaseConfig(val url: String, val maxPoolSize: Int = 10)\n\nclass ServerConfigBuilder {\n    var host: String = \"0.0.0.0\"\n    var port: Int = 8080\n    private var ssl: SslConfig? = null\n    private var database: DatabaseConfig? = null\n\n    fun ssl(certPath: String, keyPath: String) {\n        ssl = SslConfig(certPath, keyPath)\n    }\n\n    fun database(url: String, maxPoolSize: Int = 10) {\n        database = DatabaseConfig(url, maxPoolSize)\n    }\n\n    fun build(): ServerConfig = ServerConfig(host, port, ssl, database)\n}\n\nfun serverConfig(init: ServerConfigBuilder.() -> Unit): ServerConfig =\n    ServerConfigBuilder().apply(init).build()\n\n\u002F\u002F Usage\nval config = serverConfig {\n    host = \"0.0.0.0\"\n    port = 443\n    ssl(\"\u002Fcerts\u002Fcert.pem\", \"\u002Fcerts\u002Fkey.pem\")\n    database(\"jdbc:postgresql:\u002F\u002Flocalhost:5432\u002Fmydb\", maxPoolSize = 20)\n}\n```\n\n## Sequences for Lazy Evaluation\n\n```kotlin\n\u002F\u002F Good: Use sequences for large collections with multiple operations\nval result = users.asSequence()\n    .filter { it.isActive }\n    .map { it.email }\n    .filter { it.endsWith(\"@company.com\") }\n    .take(10)\n    .toList()\n\n\u002F\u002F Good: Generate infinite sequences\nval fibonacci: Sequence\u003CLong> = sequence {\n    var a = 0L\n    var b = 1L\n    while (true) {\n        yield(a)\n        val next = a + b\n        a = b\n        b = next\n    }\n}\n\nval first20 = fibonacci.take(20).toList()\n```\n\n## Gradle Kotlin DSL\n\n### build.gradle.kts Configuration\n\n```kotlin\n\u002F\u002F Check for latest versions: https:\u002F\u002Fkotlinlang.org\u002Fdocs\u002Freleases.html\nplugins {\n    kotlin(\"jvm\") version \"2.3.10\"\n    kotlin(\"plugin.serialization\") version \"2.3.10\"\n    id(\"io.ktor.plugin\") version \"3.4.0\"\n    id(\"org.jetbrains.kotlinx.kover\") version \"0.9.7\"\n    id(\"io.gitlab.arturbosch.detekt\") version \"1.23.8\"\n}\n\ngroup = \"com.example\"\nversion = \"1.0.0\"\n\nkotlin {\n    jvmToolchain(21)\n}\n\ndependencies {\n    \u002F\u002F Ktor\n    implementation(\"io.ktor:ktor-server-core:3.4.0\")\n    implementation(\"io.ktor:ktor-server-netty:3.4.0\")\n    implementation(\"io.ktor:ktor-server-content-negotiation:3.4.0\")\n    implementation(\"io.ktor:ktor-serialization-kotlinx-json:3.4.0\")\n\n    \u002F\u002F Exposed\n    implementation(\"org.jetbrains.exposed:exposed-core:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-dao:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-jdbc:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0\")\n\n    \u002F\u002F Koin\n    implementation(\"io.insert-koin:koin-ktor:4.2.0\")\n\n    \u002F\u002F Coroutines\n    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2\")\n\n    \u002F\u002F Testing\n    testImplementation(\"io.kotest:kotest-runner-junit5:6.1.4\")\n    testImplementation(\"io.kotest:kotest-assertions-core:6.1.4\")\n    testImplementation(\"io.kotest:kotest-property:6.1.4\")\n    testImplementation(\"io.mockk:mockk:1.14.9\")\n    testImplementation(\"io.ktor:ktor-server-test-host:3.4.0\")\n    testImplementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2\")\n}\n\ntasks.withType\u003CTest> {\n    useJUnitPlatform()\n}\n\ndetekt {\n    config.setFrom(files(\"config\u002Fdetekt\u002Fdetekt.yml\"))\n    buildUponDefaultConfig = true\n}\n```\n\n## Error Handling Patterns\n\n### Result Type for Domain Operations\n\n```kotlin\n\u002F\u002F Good: Use Kotlin's Result or a custom sealed class\nsuspend fun createUser(request: CreateUserRequest): Result\u003CUser> = runCatching {\n    require(request.name.isNotBlank()) { \"Name cannot be blank\" }\n    require('@' in request.email) { \"Invalid email format\" }\n\n    val user = User(\n        id = UserId(UUID.randomUUID().toString()),\n        name = request.name,\n        email = Email(request.email),\n    )\n    userRepository.save(user)\n    user\n}\n\n\u002F\u002F Good: Chain results\nval displayName = createUser(request)\n    .map { it.name }\n    .getOrElse { \"Unknown\" }\n```\n\n### require, check, error\n\n```kotlin\n\u002F\u002F Good: Preconditions with clear messages\nfun withdraw(account: Account, amount: Money): Account {\n    require(amount.value > 0) { \"Amount must be positive: $amount\" }\n    check(account.balance >= amount) { \"Insufficient balance: ${account.balance} \u003C $amount\" }\n\n    return account.copy(balance = account.balance - amount)\n}\n```\n\n## Collection Operations\n\n### Idiomatic Collection Processing\n\n```kotlin\n\u002F\u002F Good: Chained operations\nval activeAdminEmails: List\u003CString> = users\n    .filter { it.role == Role.ADMIN && it.isActive }\n    .sortedBy { it.name }\n    .map { it.email }\n\n\u002F\u002F Good: Grouping and aggregation\nval usersByRole: Map\u003CRole, List\u003CUser>> = users.groupBy { it.role }\n\nval oldestByRole: Map\u003CRole, User?> = users.groupBy { it.role }\n    .mapValues { (_, users) -> users.minByOrNull { it.createdAt } }\n\n\u002F\u002F Good: Associate for map creation\nval usersById: Map\u003CUserId, User> = users.associateBy { it.id }\n\n\u002F\u002F Good: Partition for splitting\nval (active, inactive) = users.partition { it.isActive }\n```\n\n## Quick Reference: Kotlin Idioms\n\n| Idiom | Description |\n|-------|-------------|\n| `val` over `var` | Prefer immutable variables |\n| `data class` | For value objects with equals\u002FhashCode\u002Fcopy |\n| `sealed class\u002Finterface` | For restricted type hierarchies |\n| `value class` | For type-safe wrappers with zero overhead |\n| Expression `when` | Exhaustive pattern matching |\n| Safe call `?.` | Null-safe member access |\n| Elvis `?:` | Default value for nullables |\n| `let`\u002F`apply`\u002F`also`\u002F`run`\u002F`with` | Scope functions for clean code |\n| Extension functions | Add behavior without inheritance |\n| `copy()` | Immutable updates on data classes |\n| `require`\u002F`check` | Precondition assertions |\n| Coroutine `async`\u002F`await` | Structured concurrent execution |\n| `Flow` | Cold reactive streams |\n| `sequence` | Lazy evaluation |\n| Delegation `by` | Reuse implementation without inheritance |\n\n## Anti-Patterns to Avoid\n\n```kotlin\n\u002F\u002F Bad: Force-unwrapping nullable types\nval name = user!!.name\n\n\u002F\u002F Bad: Platform type leakage from Java\nfun getLength(s: String) = s.length \u002F\u002F Safe\nfun getLength(s: String?) = s?.length ?: 0 \u002F\u002F Handle nulls from Java\n\n\u002F\u002F Bad: Mutable data classes\ndata class MutableUser(var name: String, var email: String)\n\n\u002F\u002F Bad: Using exceptions for control flow\ntry {\n    val user = findUser(id)\n} catch (e: NotFoundException) {\n    \u002F\u002F Don't use exceptions for expected cases\n}\n\n\u002F\u002F Good: Use nullable return or Result\nval user: User? = findUserOrNull(id)\n\n\u002F\u002F Bad: Ignoring coroutine scope\nGlobalScope.launch { \u002F* Avoid GlobalScope *\u002F }\n\n\u002F\u002F Good: Use structured concurrency\ncoroutineScope {\n    launch { \u002F* Properly scoped *\u002F }\n}\n\n\u002F\u002F Bad: Deeply nested scope functions\nuser?.let { u ->\n    u.address?.let { a ->\n        a.city?.let { c -> process(c) }\n    }\n}\n\n\u002F\u002F Good: Direct null-safe chain\nuser?.address?.city?.let { process(it) }\n```\n\n**Remember**: Kotlin code should be concise but readable. Leverage the type system for safety, prefer immutability, and use coroutines for concurrency. When in doubt, let the compiler help you.\n","# Kotlin Development Patterns\n\nIdiomatic Kotlin patterns and best practices for building robust, efficient, and maintainable applications.\n\n## When to Use\n\n- Writing new Kotlin code\n- Reviewing Kotlin code\n- Refactoring existing Kotlin code\n- Designing Kotlin modules or libraries\n- Configuring Gradle Kotlin DSL builds\n\n## How It Works\n\nThis skill enforces idiomatic Kotlin conventions across seven key areas: null safety using the type system and safe-call operators, immutability via `val` and `copy()` on data classes, sealed classes and interfaces for exhaustive type hierarchies, structured concurrency with coroutines and `Flow`, extension functions for adding behaviour without inheritance, type-safe DSL builders using `@DslMarker` and lambda receivers, and Gradle Kotlin DSL for build configuration.\n\n## Examples\n\n**Null safety with Elvis operator:**\n```kotlin\nfun getUserEmail(userId: String): String {\n    val user = userRepository.findById(userId)\n    return user?.email ?: \"unknown@example.com\"\n}\n```\n\n**Sealed class for exhaustive results:**\n```kotlin\nsealed class Result\u003Cout T> {\n    data class Success\u003CT>(val data: T) : Result\u003CT>()\n    data class Failure(val error: AppError) : Result\u003CNothing>()\n    data object Loading : Result\u003CNothing>()\n}\n```\n\n**Structured concurrency with async\u002Fawait:**\n```kotlin\nsuspend fun fetchUserWithPosts(userId: String): UserProfile =\n    coroutineScope {\n        val user = async { userService.getUser(userId) }\n        val posts = async { postService.getUserPosts(userId) }\n        UserProfile(user = user.await(), posts = posts.await())\n    }\n```\n\n## Core Principles\n\n### 1. Null Safety\n\nKotlin's type system distinguishes nullable and non-nullable types. Leverage it fully.\n\n```kotlin\n\u002F\u002F Good: Use non-nullable types by default\nfun getUser(id: String): User {\n    return userRepository.findById(id)\n        ?: throw UserNotFoundException(\"User $id not found\")\n}\n\n\u002F\u002F Good: Safe calls and Elvis operator\nfun getUserEmail(userId: String): String {\n    val user = userRepository.findById(userId)\n    return user?.email ?: \"unknown@example.com\"\n}\n\n\u002F\u002F Bad: Force-unwrapping nullable types\nfun getUserEmail(userId: String): String {\n    val user = userRepository.findById(userId)\n    return user!!.email \u002F\u002F Throws NPE if null\n}\n```\n\n### 2. Immutability by Default\n\nPrefer `val` over `var`, immutable collections over mutable ones.\n\n```kotlin\n\u002F\u002F Good: Immutable data\ndata class User(\n    val id: String,\n    val name: String,\n    val email: String,\n)\n\n\u002F\u002F Good: Transform with copy()\nfun updateEmail(user: User, newEmail: String): User =\n    user.copy(email = newEmail)\n\n\u002F\u002F Good: Immutable collections\nval users: List\u003CUser> = listOf(user1, user2)\nval filtered = users.filter { it.email.isNotBlank() }\n\n\u002F\u002F Bad: Mutable state\nvar currentUser: User? = null \u002F\u002F Avoid mutable global state\nval mutableUsers = mutableListOf\u003CUser>() \u002F\u002F Avoid unless truly needed\n```\n\n### 3. Expression Bodies and Single-Expression Functions\n\nUse expression bodies for concise, readable functions.\n\n```kotlin\n\u002F\u002F Good: Expression body\nfun isAdult(age: Int): Boolean = age >= 18\n\nfun formatFullName(first: String, last: String): String =\n    \"$first $last\".trim()\n\nfun User.displayName(): String =\n    name.ifBlank { email.substringBefore('@') }\n\n\u002F\u002F Good: When as expression\nfun statusMessage(code: Int): String = when (code) {\n    200 -> \"OK\"\n    404 -> \"Not Found\"\n    500 -> \"Internal Server Error\"\n    else -> \"Unknown status: $code\"\n}\n\n\u002F\u002F Bad: Unnecessary block body\nfun isAdult(age: Int): Boolean {\n    return age >= 18\n}\n```\n\n### 4. Data Classes for Value Objects\n\nUse data classes for types that primarily hold data.\n\n```kotlin\n\u002F\u002F Good: Data class with copy, equals, hashCode, toString\ndata class CreateUserRequest(\n    val name: String,\n    val email: String,\n    val role: Role = Role.USER,\n)\n\n\u002F\u002F Good: Value class for type safety (zero overhead at runtime)\n@JvmInline\nvalue class UserId(val value: String) {\n    init {\n        require(value.isNotBlank()) { \"UserId cannot be blank\" }\n    }\n}\n\n@JvmInline\nvalue class Email(val value: String) {\n    init {\n        require('@' in value) { \"Invalid email: $value\" }\n    }\n}\n\nfun getUser(id: UserId): User = userRepository.findById(id)\n```\n\n## Sealed Classes and Interfaces\n\n### Modeling Restricted Hierarchies\n\n```kotlin\n\u002F\u002F Good: Sealed class for exhaustive when\nsealed class Result\u003Cout T> {\n    data class Success\u003CT>(val data: T) : Result\u003CT>()\n    data class Failure(val error: AppError) : Result\u003CNothing>()\n    data object Loading : Result\u003CNothing>()\n}\n\nfun \u003CT> Result\u003CT>.getOrNull(): T? = when (this) {\n    is Result.Success -> data\n    is Result.Failure -> null\n    is Result.Loading -> null\n}\n\nfun \u003CT> Result\u003CT>.getOrThrow(): T = when (this) {\n    is Result.Success -> data\n    is Result.Failure -> throw error.toException()\n    is Result.Loading -> throw IllegalStateException(\"Still loading\")\n}\n```\n\n### Sealed Interfaces for API Responses\n\n```kotlin\nsealed interface ApiError {\n    val message: String\n\n    data class NotFound(override val message: String) : ApiError\n    data class Unauthorized(override val message: String) : ApiError\n    data class Validation(\n        override val message: String,\n        val field: String,\n    ) : ApiError\n    data class Internal(\n        override val message: String,\n        val cause: Throwable? = null,\n    ) : ApiError\n}\n\nfun ApiError.toStatusCode(): Int = when (this) {\n    is ApiError.NotFound -> 404\n    is ApiError.Unauthorized -> 401\n    is ApiError.Validation -> 422\n    is ApiError.Internal -> 500\n}\n```\n\n## Scope Functions\n\n### When to Use Each\n\n```kotlin\n\u002F\u002F let: Transform nullable or scoped result\nval length: Int? = name?.let { it.trim().length }\n\n\u002F\u002F apply: Configure an object (returns the object)\nval user = User().apply {\n    name = \"Alice\"\n    email = \"alice@example.com\"\n}\n\n\u002F\u002F also: Side effects (returns the object)\nval user = createUser(request).also { logger.info(\"Created user: ${it.id}\") }\n\n\u002F\u002F run: Execute a block with receiver (returns result)\nval result = connection.run {\n    prepareStatement(sql)\n    executeQuery()\n}\n\n\u002F\u002F with: Non-extension form of run\nval csv = with(StringBuilder()) {\n    appendLine(\"name,email\")\n    users.forEach { appendLine(\"${it.name},${it.email}\") }\n    toString()\n}\n```\n\n### Anti-Patterns\n\n```kotlin\n\u002F\u002F Bad: Nesting scope functions\nuser?.let { u ->\n    u.address?.let { addr ->\n        addr.city?.let { city ->\n            println(city) \u002F\u002F Hard to read\n        }\n    }\n}\n\n\u002F\u002F Good: Chain safe calls instead\nval city = user?.address?.city\ncity?.let { println(it) }\n```\n\n## Extension Functions\n\n### Adding Functionality Without Inheritance\n\n```kotlin\n\u002F\u002F Good: Domain-specific extensions\nfun String.toSlug(): String =\n    lowercase()\n        .replace(Regex(\"[^a-z0-9\\\\s-]\"), \"\")\n        .replace(Regex(\"\\\\s+\"), \"-\")\n        .trim('-')\n\nfun Instant.toLocalDate(zone: ZoneId = ZoneId.systemDefault()): LocalDate =\n    atZone(zone).toLocalDate()\n\n\u002F\u002F Good: Collection extensions\nfun \u003CT> List\u003CT>.second(): T = this[1]\n\nfun \u003CT> List\u003CT>.secondOrNull(): T? = getOrNull(1)\n\n\u002F\u002F Good: Scoped extensions (not polluting global namespace)\nclass UserService {\n    private fun User.isActive(): Boolean =\n        status == Status.ACTIVE && lastLogin.isAfter(Instant.now().minus(30, ChronoUnit.DAYS))\n\n    fun getActiveUsers(): List\u003CUser> = userRepository.findAll().filter { it.isActive() }\n}\n```\n\n## Coroutines\n\n### Structured Concurrency\n\n```kotlin\n\u002F\u002F Good: Structured concurrency with coroutineScope\nsuspend fun fetchUserWithPosts(userId: String): UserProfile =\n    coroutineScope {\n        val userDeferred = async { userService.getUser(userId) }\n        val postsDeferred = async { postService.getUserPosts(userId) }\n\n        UserProfile(\n            user = userDeferred.await(),\n            posts = postsDeferred.await(),\n        )\n    }\n\n\u002F\u002F Good: supervisorScope when children can fail independently\nsuspend fun fetchDashboard(userId: String): Dashboard =\n    supervisorScope {\n        val user = async { userService.getUser(userId) }\n        val notifications = async { notificationService.getRecent(userId) }\n        val recommendations = async { recommendationService.getFor(userId) }\n\n        Dashboard(\n            user = user.await(),\n            notifications = try {\n                notifications.await()\n            } catch (e: CancellationException) {\n                throw e\n            } catch (e: Exception) {\n                emptyList()\n            },\n            recommendations = try {\n                recommendations.await()\n            } catch (e: CancellationException) {\n                throw e\n            } catch (e: Exception) {\n                emptyList()\n            },\n        )\n    }\n```\n\n### Flow for Reactive Streams\n\n```kotlin\n\u002F\u002F Good: Cold flow with proper error handling\nfun observeUsers(): Flow\u003CList\u003CUser>> = flow {\n    while (currentCoroutineContext().isActive) {\n        val users = userRepository.findAll()\n        emit(users)\n        delay(5.seconds)\n    }\n}.catch { e ->\n    logger.error(\"Error observing users\", e)\n    emit(emptyList())\n}\n\n\u002F\u002F Good: Flow operators\nfun searchUsers(query: Flow\u003CString>): Flow\u003CList\u003CUser>> =\n    query\n        .debounce(300.milliseconds)\n        .distinctUntilChanged()\n        .filter { it.length >= 2 }\n        .mapLatest { q -> userRepository.search(q) }\n        .catch { emit(emptyList()) }\n```\n\n### Cancellation and Cleanup\n\n```kotlin\n\u002F\u002F Good: Respect cancellation\nsuspend fun processItems(items: List\u003CItem>) {\n    items.forEach { item ->\n        ensureActive() \u002F\u002F Check cancellation before expensive work\n        processItem(item)\n    }\n}\n\n\u002F\u002F Good: Cleanup with try\u002Ffinally\nsuspend fun acquireAndProcess() {\n    val resource = acquireResource()\n    try {\n        resource.process()\n    } finally {\n        withContext(NonCancellable) {\n            resource.release() \u002F\u002F Always release, even on cancellation\n        }\n    }\n}\n```\n\n## Delegation\n\n### Property Delegation\n\n```kotlin\n\u002F\u002F Lazy initialization\nval expensiveData: List\u003CUser> by lazy {\n    userRepository.findAll()\n}\n\n\u002F\u002F Observable property\nvar name: String by Delegates.observable(\"initial\") { _, old, new ->\n    logger.info(\"Name changed from '$old' to '$new'\")\n}\n\n\u002F\u002F Map-backed properties\nclass Config(private val map: Map\u003CString, Any?>) {\n    val host: String by map\n    val port: Int by map\n    val debug: Boolean by map\n}\n\nval config = Config(mapOf(\"host\" to \"localhost\", \"port\" to 8080, \"debug\" to true))\n```\n\n### Interface Delegation\n\n```kotlin\n\u002F\u002F Good: Delegate interface implementation\nclass LoggingUserRepository(\n    private val delegate: UserRepository,\n    private val logger: Logger,\n) : UserRepository by delegate {\n    \u002F\u002F Only override what you need to add logging to\n    override suspend fun findById(id: String): User? {\n        logger.info(\"Finding user by id: $id\")\n        return delegate.findById(id).also {\n            logger.info(\"Found user: ${it?.name ?: \"null\"}\")\n        }\n    }\n}\n```\n\n## DSL Builders\n\n### Type-Safe Builders\n\n```kotlin\n\u002F\u002F Good: DSL with @DslMarker\n@DslMarker\nannotation class HtmlDsl\n\n@HtmlDsl\nclass HTML {\n    private val children = mutableListOf\u003CElement>()\n\n    fun head(init: Head.() -> Unit) {\n        children += Head().apply(init)\n    }\n\n    fun body(init: Body.() -> Unit) {\n        children += Body().apply(init)\n    }\n\n    override fun toString(): String = children.joinToString(\"\\n\")\n}\n\nfun html(init: HTML.() -> Unit): HTML = HTML().apply(init)\n\n\u002F\u002F Usage\nval page = html {\n    head { title(\"My Page\") }\n    body {\n        h1(\"Welcome\")\n        p(\"Hello, World!\")\n    }\n}\n```\n\n### Configuration DSL\n\n```kotlin\ndata class ServerConfig(\n    val host: String = \"0.0.0.0\",\n    val port: Int = 8080,\n    val ssl: SslConfig? = null,\n    val database: DatabaseConfig? = null,\n)\n\ndata class SslConfig(val certPath: String, val keyPath: String)\ndata class DatabaseConfig(val url: String, val maxPoolSize: Int = 10)\n\nclass ServerConfigBuilder {\n    var host: String = \"0.0.0.0\"\n    var port: Int = 8080\n    private var ssl: SslConfig? = null\n    private var database: DatabaseConfig? = null\n\n    fun ssl(certPath: String, keyPath: String) {\n        ssl = SslConfig(certPath, keyPath)\n    }\n\n    fun database(url: String, maxPoolSize: Int = 10) {\n        database = DatabaseConfig(url, maxPoolSize)\n    }\n\n    fun build(): ServerConfig = ServerConfig(host, port, ssl, database)\n}\n\nfun serverConfig(init: ServerConfigBuilder.() -> Unit): ServerConfig =\n    ServerConfigBuilder().apply(init).build()\n\n\u002F\u002F Usage\nval config = serverConfig {\n    host = \"0.0.0.0\"\n    port = 443\n    ssl(\"\u002Fcerts\u002Fcert.pem\", \"\u002Fcerts\u002Fkey.pem\")\n    database(\"jdbc:postgresql:\u002F\u002Flocalhost:5432\u002Fmydb\", maxPoolSize = 20)\n}\n```\n\n## Sequences for Lazy Evaluation\n\n```kotlin\n\u002F\u002F Good: Use sequences for large collections with multiple operations\nval result = users.asSequence()\n    .filter { it.isActive }\n    .map { it.email }\n    .filter { it.endsWith(\"@company.com\") }\n    .take(10)\n    .toList()\n\n\u002F\u002F Good: Generate infinite sequences\nval fibonacci: Sequence\u003CLong> = sequence {\n    var a = 0L\n    var b = 1L\n    while (true) {\n        yield(a)\n        val next = a + b\n        a = b\n        b = next\n    }\n}\n\nval first20 = fibonacci.take(20).toList()\n```\n\n## Gradle Kotlin DSL\n\n### build.gradle.kts Configuration\n\n```kotlin\n\u002F\u002F Check for latest versions: https:\u002F\u002Fkotlinlang.org\u002Fdocs\u002Freleases.html\nplugins {\n    kotlin(\"jvm\") version \"2.3.10\"\n    kotlin(\"plugin.serialization\") version \"2.3.10\"\n    id(\"io.ktor.plugin\") version \"3.4.0\"\n    id(\"org.jetbrains.kotlinx.kover\") version \"0.9.7\"\n    id(\"io.gitlab.arturbosch.detekt\") version \"1.23.8\"\n}\n\ngroup = \"com.example\"\nversion = \"1.0.0\"\n\nkotlin {\n    jvmToolchain(21)\n}\n\ndependencies {\n    \u002F\u002F Ktor\n    implementation(\"io.ktor:ktor-server-core:3.4.0\")\n    implementation(\"io.ktor:ktor-server-netty:3.4.0\")\n    implementation(\"io.ktor:ktor-server-content-negotiation:3.4.0\")\n    implementation(\"io.ktor:ktor-serialization-kotlinx-json:3.4.0\")\n\n    \u002F\u002F Exposed\n    implementation(\"org.jetbrains.exposed:exposed-core:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-dao:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-jdbc:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0\")\n\n    \u002F\u002F Koin\n    implementation(\"io.insert-koin:koin-ktor:4.2.0\")\n\n    \u002F\u002F Coroutines\n    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2\")\n\n    \u002F\u002F Testing\n    testImplementation(\"io.kotest:kotest-runner-junit5:6.1.4\")\n    testImplementation(\"io.kotest:kotest-assertions-core:6.1.4\")\n    testImplementation(\"io.kotest:kotest-property:6.1.4\")\n    testImplementation(\"io.mockk:mockk:1.14.9\")\n    testImplementation(\"io.ktor:ktor-server-test-host:3.4.0\")\n    testImplementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2\")\n}\n\ntasks.withType\u003CTest> {\n    useJUnitPlatform()\n}\n\ndetekt {\n    config.setFrom(files(\"config\u002Fdetekt\u002Fdetekt.yml\"))\n    buildUponDefaultConfig = true\n}\n```\n\n## Error Handling Patterns\n\n### Result Type for Domain Operations\n\n```kotlin\n\u002F\u002F Good: Use Kotlin's Result or a custom sealed class\nsuspend fun createUser(request: CreateUserRequest): Result\u003CUser> = runCatching {\n    require(request.name.isNotBlank()) { \"Name cannot be blank\" }\n    require('@' in request.email) { \"Invalid email format\" }\n\n    val user = User(\n        id = UserId(UUID.randomUUID().toString()),\n        name = request.name,\n        email = Email(request.email),\n    )\n    userRepository.save(user)\n    user\n}\n\n\u002F\u002F Good: Chain results\nval displayName = createUser(request)\n    .map { it.name }\n    .getOrElse { \"Unknown\" }\n```\n\n### require, check, error\n\n```kotlin\n\u002F\u002F Good: Preconditions with clear messages\nfun withdraw(account: Account, amount: Money): Account {\n    require(amount.value > 0) { \"Amount must be positive: $amount\" }\n    check(account.balance >= amount) { \"Insufficient balance: ${account.balance} \u003C $amount\" }\n\n    return account.copy(balance = account.balance - amount)\n}\n```\n\n## Collection Operations\n\n### Idiomatic Collection Processing\n\n```kotlin\n\u002F\u002F Good: Chained operations\nval activeAdminEmails: List\u003CString> = users\n    .filter { it.role == Role.ADMIN && it.isActive }\n    .sortedBy { it.name }\n    .map { it.email }\n\n\u002F\u002F Good: Grouping and aggregation\nval usersByRole: Map\u003CRole, List\u003CUser>> = users.groupBy { it.role }\n\nval oldestByRole: Map\u003CRole, User?> = users.groupBy { it.role }\n    .mapValues { (_, users) -> users.minByOrNull { it.createdAt } }\n\n\u002F\u002F Good: Associate for map creation\nval usersById: Map\u003CUserId, User> = users.associateBy { it.id }\n\n\u002F\u002F Good: Partition for splitting\nval (active, inactive) = users.partition { it.isActive }\n```\n\n## Quick Reference: Kotlin Idioms\n\n| Idiom | Description |\n|-------|-------------|\n| `val` over `var` | Prefer immutable variables |\n| `data class` | For value objects with equals\u002FhashCode\u002Fcopy |\n| `sealed class\u002Finterface` | For restricted type hierarchies |\n| `value class` | For type-safe wrappers with zero overhead |\n| Expression `when` | Exhaustive pattern matching |\n| Safe call `?.` | Null-safe member access |\n| Elvis `?:` | Default value for nullables |\n| `let`\u002F`apply`\u002F`also`\u002F`run`\u002F`with` | Scope functions for clean code |\n| Extension functions | Add behavior without inheritance |\n| `copy()` | Immutable updates on data classes |\n| `require`\u002F`check` | Precondition assertions |\n| Coroutine `async`\u002F`await` | Structured concurrent execution |\n| `Flow` | Cold reactive streams |\n| `sequence` | Lazy evaluation |\n| Delegation `by` | Reuse implementation without inheritance |\n\n## Anti-Patterns to Avoid\n\n```kotlin\n\u002F\u002F Bad: Force-unwrapping nullable types\nval name = user!!.name\n\n\u002F\u002F Bad: Platform type leakage from Java\nfun getLength(s: String) = s.length \u002F\u002F Safe\nfun getLength(s: String?) = s?.length ?: 0 \u002F\u002F Handle nulls from Java\n\n\u002F\u002F Bad: Mutable data classes\ndata class MutableUser(var name: String, var email: String)\n\n\u002F\u002F Bad: Using exceptions for control flow\ntry {\n    val user = findUser(id)\n} catch (e: NotFoundException) {\n    \u002F\u002F Don't use exceptions for expected cases\n}\n\n\u002F\u002F Good: Use nullable return or Result\nval user: User? = findUserOrNull(id)\n\n\u002F\u002F Bad: Ignoring coroutine scope\nGlobalScope.launch { \u002F* Avoid GlobalScope *\u002F }\n\n\u002F\u002F Good: Use structured concurrency\ncoroutineScope {\n    launch { \u002F* Properly scoped *\u002F }\n}\n\n\u002F\u002F Bad: Deeply nested scope functions\nuser?.let { u ->\n    u.address?.let { a ->\n        a.city?.let { c -> process(c) }\n    }\n}\n\n\u002F\u002F Good: Direct null-safe chain\nuser?.address?.city?.let { process(it) }\n```\n\n**Remember**: Kotlin code should be concise but readable. Leverage the type system for safety, prefer immutability, and use coroutines for concurrency. When in doubt, let the compiler help you.\n",{"name":5,"origin":32,"description":7},"ECC","main","a298c929b6cbd662e1fce54302ce520d5ceea1a5",".agents\u002Flibrary\u002Fskills\u002Fkotlin-patterns","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain","https:\u002F\u002Fgithub.com\u002FPrekzursil","2026-11-02 14:30:57",{"source":8,"skillName":5,"commands":40,"prompt":50,"zipAvailable":51},[41,44,47],{"tool":42,"command":43},"npx","npx skills add Prekzursil\u002Fagent-skills-toolchain --skill kotlin-patterns",{"tool":45,"command":46},"bunx","bunx skills add Prekzursil\u002Fagent-skills-toolchain --skill kotlin-patterns",{"tool":48,"command":49},"pnpm","pnpm dlx skills add Prekzursil\u002Fagent-skills-toolchain --skill kotlin-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Flibrary\u002Fskills\u002Fkotlin-patterns\u002FSKILL.md",true,[53,59,63,67,71,77,81,85,89,93,97,101,105,109,113,117,121,125,129,133,137],{"path":54,"resourceType":55,"resourceTypeDesc":56,"sizeBytes":57,"sourceHtmlUrl":58},"references\u002Fanalysis_criteria.md","REFERENCE","参考文档",2334,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Freferences\u002Fanalysis_criteria.md",{"path":60,"resourceType":55,"resourceTypeDesc":56,"sizeBytes":61,"sourceHtmlUrl":62},"references\u002Fschema.md",3521,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Freferences\u002Fschema.md",{"path":64,"resourceType":55,"resourceTypeDesc":56,"sizeBytes":65,"sourceHtmlUrl":66},"references\u002Fsecurity_patterns.md",1782,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Freferences\u002Fsecurity_patterns.md",{"path":68,"resourceType":55,"resourceTypeDesc":56,"sizeBytes":69,"sourceHtmlUrl":70},"references\u002Fskill_template.md",2981,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Freferences\u002Fskill_template.md",{"path":72,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":75,"sourceHtmlUrl":76},"scripts\u002Fanalyzers\u002F__init__.py","SCRIPT","脚本",357,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fanalyzers\u002F__init__.py",{"path":78,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":79,"sourceHtmlUrl":80},"scripts\u002Fanalyzers\u002Fcode_quality.py",9822,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fanalyzers\u002Fcode_quality.py",{"path":82,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":83,"sourceHtmlUrl":84},"scripts\u002Fanalyzers\u002Fcross_skill.py",5544,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fanalyzers\u002Fcross_skill.py",{"path":86,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":87,"sourceHtmlUrl":88},"scripts\u002Fanalyzers\u002Fdependencies.py",4566,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fanalyzers\u002Fdependencies.py",{"path":90,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":91,"sourceHtmlUrl":92},"scripts\u002Fanalyzers\u002Fdocumentation.py",6839,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fanalyzers\u002Fdocumentation.py",{"path":94,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":95,"sourceHtmlUrl":96},"scripts\u002Fanalyzers\u002Fgovernance_audit.py",5824,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fanalyzers\u002Fgovernance_audit.py",{"path":98,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":99,"sourceHtmlUrl":100},"scripts\u002Fanalyzers\u002Fperformance.py",6309,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fanalyzers\u002Fperformance.py",{"path":102,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":103,"sourceHtmlUrl":104},"scripts\u002Fanalyzers\u002Fsecurity.py",7647,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fanalyzers\u002Fsecurity.py",{"path":106,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":107,"sourceHtmlUrl":108},"scripts\u002Fconfig.py",6292,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fconfig.py",{"path":110,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":111,"sourceHtmlUrl":112},"scripts\u002Fcost_optimizer.py",5923,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fcost_optimizer.py",{"path":114,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":115,"sourceHtmlUrl":116},"scripts\u002Fdb.py",14002,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fdb.py",{"path":118,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":119,"sourceHtmlUrl":120},"scripts\u002Fgovernance.py",1876,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fgovernance.py",{"path":122,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":123,"sourceHtmlUrl":124},"scripts\u002Frecommender.py",8674,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Frecommender.py",{"path":126,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":127,"sourceHtmlUrl":128},"scripts\u002Freport_generator.py",8819,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Freport_generator.py",{"path":130,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":131,"sourceHtmlUrl":132},"scripts\u002Frequirements.txt",12,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Frequirements.txt",{"path":134,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":135,"sourceHtmlUrl":136},"scripts\u002Frun_audit.py",10245,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Frun_audit.py",{"path":138,"resourceType":73,"resourceTypeDesc":74,"sizeBytes":139,"sourceHtmlUrl":140},"scripts\u002Fscanner.py",9871,"https:\u002F\u002Fgithub.com\u002Fsapsapshen\u002Fai-memory-router\u002Fblob\u002Fmain\u002Fskills-store\u002Fglobal\u002Fskill-sentinel\u002Fscripts\u002Fscanner.py",[142,164,195,222,236,252],{"skillNo":143,"name":144,"nameZh":6,"description":145,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":146,"categories":147,"keywords":148,"publishedAt":27},"01M6DF7D7478R55P46REYH4QAC","clarify-requirements","Resolve genuine pre-execution ambiguity by scanning repo context, generating 1-6 grounded options, and asking 1-3 focused questions via AskUserQuestion. NOT for mid-task use (injection risk); NOT when the goal is already unambiguous; use to disambiguate vague prompts before any file edits.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fworkspace-hooks\u002Fskills\u002Fclarify-requirements\u002FSKILL.md",[],[149,150,151,152,153,154,155,156,157,158,159,160,161,162,163],"clarify requirements","clarify","requirements","risk","options","task","prompts","goal","scanning","use","context","repo","injection","execution","file",{"skillNo":165,"name":166,"nameZh":6,"description":167,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":168,"categories":169,"keywords":175,"publishedAt":27},"01M6DF79CA4BHJS66J8EZDBPWJ","openmm-md","Run FREE, open-source, Windows-native GPU molecular dynamics via the openmm-md MCP (OpenMM + CUDA on the local NVIDIA GPU). Use for protein \u002F protein-ligand MD when you want a free stack or are on Windows without a Schrodinger license. Covers the full workflow (prepare, solvate, minimize, equilibrate, production, analyze, mmgbsa) AND an honest decision guide vs Schrodinger Desmond, including how to run Desmond legitimately on Linux or a cloud GPU with a real license. This is a deliberately LESSER free alternative and does NOT replace the schrodinger MCP tools. Do NOT use it to pick a free tool when a validated FEP+, WaterMap, IFD-MD, or Prime MM-GBSA result is actually required.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fmcp\u002Fopenmm-md-mcp\u002Fskill\u002Fopenmm-md\u002FSKILL.md",[170],{"categoryNo":171,"categoryName":172,"taxonomyType":173,"taxonomyTypeDesc":174},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[176,177,178,179,180,181,182,183,184,185,186,187,188,189,158,190,191,192,193,194],"openmm md","how","decision","pick","linux","analyze","tool","stack","open","NVIDIA","gpu","license","workflow","windows","free","molecular dynamics","local","do","MCP",{"skillNo":196,"name":197,"nameZh":6,"description":198,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":199,"categories":200,"keywords":207,"publishedAt":27},"01M6DF76C1MG85VEWP8VZCBNMN","visual-review","Take screenshots of web pages and UI using Playwright for visual review and iteration","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fvisual-review\u002FSKILL.md",[201,204],{"categoryNo":202,"categoryName":203,"taxonomyType":173,"taxonomyTypeDesc":174},"01JBSEEDCAT000000000000003","演示文稿",{"categoryNo":205,"categoryName":206,"taxonomyType":173,"taxonomyTypeDesc":174},"01JBSEEDCAT000000000000008","测试与质量",[208,209,210,211,212,213,214,215,216,217,177,218,219,220,221],"review","visual","visual review","pages","web","false","iteration","take","check","slides","ui","screenshot","playwright","screenshots",{"skillNo":223,"name":224,"nameZh":6,"description":225,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":226,"categories":227,"keywords":228,"publishedAt":27},"01M6DF73AQVCBZKEN0YXEX9FXP","status","Diagnostic status report — shows metaswarm installation state, project setup, and potential issues","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstatus\u002FSKILL.md",[],[224,229,230,231,232,233,234,235],"setup","report","project","installation","project setup","issues","state",{"skillNo":237,"name":238,"nameZh":6,"description":239,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":240,"categories":241,"keywords":243,"publishedAt":27},"01M6DF6X6C2XB0P6GX76P7TTYV","start","Use when starting work on any task, when the user mentions metaswarm, or when the user wants to begin tracked development work","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstart\u002FSKILL.md",[242],{"categoryNo":171,"categoryName":172,"taxonomyType":173,"taxonomyTypeDesc":174},[238,244,154,245,246,247,158,248,249,250,251],"start issue","issue","ready","work on issue","user","work","work on","development",{"skillNo":253,"name":229,"nameZh":6,"description":254,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":255,"categories":256,"keywords":257,"publishedAt":27},"01M6DF6QMJ9AJKFKHHKRT005PQ","Interactive project setup — detects your project, configures metaswarm, writes project-local files","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fsetup\u002FSKILL.md",[],[229,231,192,258,233,259],"files","interactive"]