首页
/ ECC 的 Cursor Kotlin 规则:kotlin-patterns.md 如何为 .kt 文件注入 Kotlin 惯用模式约束

ECC 的 Cursor Kotlin 规则:kotlin-patterns.md 如何为 .kt 文件注入 Kotlin 惯用模式约束

2026-09-06 11:58:34作者:魏侃纯Zoe

本文以 ECC 仓库中 kotlin-patterns.md 规则文件为主体,完整拆解 Cursor 规则的前置元数据(globs 匹配与按需加载机制)、Sealed 类、扩展函数、作用域函数与 Koin 依赖注入四大 Kotlin 模式约束,并结合同目录规则族、kotlin-patterns skillCursor 安装目标 的源码,说明这条规则在 ECC agent harness 中如何被触发、如何延伸、又如何分发到实际项目中。读完你可以掌握:为 Cursor 编写语言级规则文件的方法、规则与 skill 的分层协作关系,以及一套可直接落地的 Kotlin 编码约束。

一、规则文件本体:一个“按需加载”的 Kotlin 模式约束

.cursor/rules/kotlin-patterns.md 是 ECC 为 Cursor 编辑器提供的 Kotlin 语言规则文件。与 alwaysApply: true 的全局规则不同,它通过前置元数据声明自己的适用范围,只在编辑 Kotlin 相关文件时才注入上下文:

---
description: "Kotlin patterns extending common rules"
globs: ["**/*.kt", "**/*.kts", "**/build.gradle.kts"]
alwaysApply: false
---

三个字段的含义:

  • globs:文件匹配模式。**/*.kt**/*.kts 覆盖 Kotlin 源码和 Kotlin 脚本,**/build.gradle.kts 把 Gradle Kotlin DSL 构建脚本也纳入约束——这与 kotlin-patterns skill 中“Configuring Gradle Kotlin DSL builds”的使用场景对应;
  • alwaysApply: false:规则不随会话常驻,仅在 globs 命中时被加载,避免污染其他语言任务的上下文预算;
  • description:向 Agent 说明该规则定位——“在 common 规则基础上扩展 Kotlin 专属内容”。

从文件结构看,正文以一行提示 This file extends the common patterns rule with Kotlin-specific content 开篇,明确它是一条增量规则:基础约束来自 common-patterns.md,本文件只补充 Kotlin 专属部分。这种“common 打底 + 语言扩展”的分层写法在 .cursor/rules/ 目录中是统一模式,Kotlin 规则族共五个文件:

规则文件 覆盖主题
kotlin-coding-style.md 格式化(ktfmt/ktlint)、不可变性、空安全、表达式体
kotlin-patterns.md Sealed 类、扩展函数、作用域函数、依赖注入
kotlin-testing.md Kotest + MockK、runTest 协程测试、Kover 覆盖率
kotlin-security.md Kotlin 安全约束
kotlin-hooks.md Kotlin 相关的 hook 配置

五个文件使用完全相同的 globs 声明,意味着打开任意 .kt 文件时,这一整套约束会同时生效——模式、风格、测试、安全形成闭环。

二、Sealed Classes:用密封类型建模穷举层级

规则给出的核心示例是泛型 Result 类型:

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Failure(val error: AppError) : Result<Nothing>()
}

要点解析:

  • sealed class 声明受限制的继承层级,所有子类必须在同一文件内定义,使 when 表达式可以做到编译期穷举——遗漏分支直接报错,而不是运行时才暴露;
  • out T 是泛型协变声明,允许 Success<Nothing> 这类子类型关系成立;
  • Failure 声明为 Result<Nothing>,表达“失败分支不携带成功数据”这一语义,Nothing 是 Kotlin 的底部类型(top-level 中任何类型都是它的子类型)。

这与 common 规则中 API Response Format 的“统一响应包络”思想一脉相承:用类型系统而非约定俗成的字段命名来强制 success/data/error 的互斥关系。

在 ECC 的 kotlin-patterns skill 中,这个模式被进一步扩展为带 Loading 分支的三态版本,并配上了穷举消费函数:

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Failure(val error: AppError) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

fun <T> Result<T>.getOrNull(): T? = when (this) {
    is Result.Success -> data
    is Result.Failure -> null
    is Result.Loading -> null
}

skill 中还给出了 sealed interface 建模 API 错误的进阶用法(ApiError.NotFound/Unauthorized/Validation/Internal 各自映射到 404/401/422/500 状态码),展示了同一“穷举层级”思想在错误域的应用。规则文件给出最小可用形态,skill 给出完整版——这正是 ECC “规则管底线、skill 管深度”的分层设计。

三、Extension Functions:不继承、不污染的全局扩展

规则给出的示例是把行为“加”到 String 上,且限定在使用的地方:

fun String.toSlug(): String =
    lowercase().replace(Regex("[^a-z0-9\\s-]"), "").replace(Regex("\\s+"), "-")

“scoped to where they're used” 这句约束的含义,在 skill 的示例中有明确落地——作用域化的扩展函数:把扩展函数声明为类内部的 private 成员,避免污染全局命名空间:

class UserService {
    private fun User.isActive(): Boolean =
        status == Status.ACTIVE && lastLogin.isAfter(Instant.now().minus(30, ChronoUnit.DAYS))

    fun getActiveUsers(): List<User> = userRepository.findAll().filter { it.isActive() }
}

此外 skill 补充了两个实用形态:带默认参数的时间转换扩展(Instant.toLocalDate(zone))和集合扩展(List<T>.secondOrNull() 基于标准库 getOrNull(1) 一行实现),说明规则约束的是“扩展函数应当领域化、私有化、可测试”,而非仅仅“可以用扩展函数”。

四、Scope Functions:四种作用域函数的分工与反模式

规则对五个作用域函数给出了明确的职责划分(规则原文列了三个,skill 补全了 run/with):

  • let:转换可空或受限结果,返回 lambda 结果——val length: Int? = name?.let { it.trim().length }
  • apply:配置对象,返回对象本身——User().apply { name = "Alice"; email = "..." }
  • also:副作用(日志、埋点),返回对象本身——createUser(request).also { logger.info("Created user: ${it.id}") }
  • run:带接收者的块执行,返回 lambda 结果——connection.run { prepareStatement(sql); executeQuery() }
  • withrun 的非扩展形式——with(StringBuilder()) { appendLine(...); toString() }

规则明确禁止的一条反模式是嵌套作用域函数,skill 给出了对照示例:

// Bad: Nesting scope functions
user?.let { u ->
    u.address?.let { addr ->
        addr.city?.let { city -> println(city) } // 嵌套三层,可读性崩塌
    }
}

// Good: Chain safe calls instead
val city = user?.address?.city
city?.let { println(it) }

这条约束与同目录 kotlin-coding-style.md 中“避免 !!,使用 ?.?:require”的空安全条款互相配合:安全调用链优先,作用域函数只在确有“转换/配置/副作用”语义时使用。

五、Dependency Injection:Koin 模块在 Ktor 项目中的声明方式

规则给出的是 Koin 声明式模块,且特意绑定 Ktor 场景(规则原文):

val appModule = module {
    single<UserRepository> { ExposedUserRepository(get()) }
    single { UserService(get()) }
}
  • single<T> 注册单例,{ ExposedUserRepository(get()) } 中的 get() 在装配时从容器解析依赖(这里解析的是数据库连接/事务对象),实现构造器注入而非字段注入;
  • single { UserService(get()) } 隐式按类型注册 UserServiceget() 拉取 UserRepository
  • 从 skill 的 Gradle Kotlin DSL 配置 看,ECC 的推荐技术栈组合是 Ktor 3.4.0 + Exposed 1.0.0 + Koin 4.2.0(io.insert-koin:koin-ktor)+ kotlinx-coroutines 1.10.2,Koin 模块即作为该栈的标准 DI 层。

规则层面更完整的版本(来自 rules/kotlin/patterns.md,它是本规则在非 Cursor 平台的对应物)区分了两种 DI 选型:KMP 项目用 Koin,纯 Android 项目用 Hilt

// Koin — declare modules
val dataModule = module {
    single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
    factory { GetItemsUseCase(get()) }
    viewModelOf(::ItemListViewModel)
}

注意 factory(每次解析新建,适合无状态 UseCase 与 ViewModel 之外的短生命周期对象)与 single 的生命周期区别,这是规则文件最小示例中未展开、但实操中必须理解的参数差异。

六、从规则到 Skill:kotlin-patterns 的引用闭环

规则文件末尾的 Reference 段落指向 skill:

See skill: kotlin-patterns for comprehensive Kotlin patterns including coroutines, DSL builders, and delegation.

在 ECC 的架构中,.cursor/rules/常驻约束层(短小、可按 globs 自动加载),skills/深度知识层(按需激活的完整手册)。两者内容同源且互相呼应:

  • 规则文件的 Sealed 类示例(两态 Result)是 skill 中三态 Result 的简化版;
  • 规则文件的 toSlug() 扩展函数在 skill 中补齐了 .trim('-') 收尾和“作用域化扩展”最佳实践;
  • 规则文件未覆盖的协程、DSL、委托三大主题,全部由 skill 承接。

skill 额外提供的关键模式(读者若需完整 Kotlin 工程约束应一并阅读 skills/kotlin-patterns/SKILL.md):

结构化并发——coroutineScope 并行取数、supervisorScope 让子任务失败互相独立:

suspend fun fetchUserWithPosts(userId: String): UserProfile =
    coroutineScope {
        val user = async { userService.getUser(userId) }
        val posts = async { postService.getUserPosts(userId) }
        UserProfile(user = user.await(), posts = posts.await())
    }

类型安全 DSL Builder——用 @DslMarker 防止隐式接收者歧义:

@DslMarker
annotation class HtmlDsl

@HtmlDsl
class HTML {
    fun body(init: Body.() -> Unit) { children += Body().apply(init) }
    // ...
}

fun html(init: HTML.() -> Unit): HTML = HTML().apply(init)

接口委托——UserRepository by delegate 一行实现透传,只覆写需要加日志的方法。

此外 skill 末尾的“Quick Reference”表格把 16 条 Kotlin 惯用法(val over varvalue classwhen 表达式、Flowsequence 懒求值、by 委托等)汇总为速查表,可直接作为 code review 的 checklist 使用。

七、配套约束:构建配置与测试规则如何咬合

Kotlin 规则族中另外两个文件为 patterns 规则提供了执行保障:

构建层kotlin-patterns skill 给出了完整的 build.gradle.kts 参考配置,关键项包括:

plugins {
    kotlin("jvm") version "2.3.10"
    id("io.ktor.plugin") version "3.4.0"
    id("org.jetbrains.kotlinx.kover") version "0.9.7"
    id("io.gitlab.arturbosch.detekt") version "1.23.8"
}

kotlin { jvmToolchain(21) }

detekt {
    config.setFrom(files("config/detekt/detekt.yml"))
    buildUponDefaultConfig = true
}

globs 中包含 **/build.gradle.kts 的意义在此体现:规则不仅约束业务代码,也约束构建脚本本身的写法(Kotlin DSL 风格、依赖版本管理)。

测试层kotlin-testing.md 规定使用 Kotest(StringSpec/FunSpec/BehaviorSpec 风格)+ MockK 做 mock,协程代码统一用 runTest

test("async operation completes") {
    runTest {
        val result = service.fetchData()
        result.shouldNotBeEmpty()
    }
}

覆盖率由 Kover 报告。这意味着 patterns 规则里要求写出的 Result<T> 返回值,在测试规则里有对应的验证手段——suspend fun getById(id: String): Result<Item> 这类接口(见 rules/kotlin/patterns.md 的 Repository 模式)天然适合 Kotest 断言。

八、分发机制:规则如何进入用户项目

从源码结构看,.cursor/rules/ 并非仅供本仓库使用,而是 ECC 安装器面向 Cursor 平台的分发源。scripts/lib/install-targets/cursor-project.js 中存在 sourceRelativePath: '.cursor/rules' 的声明,将规则目录整体作为安装操作的目标路径之一;tests/lib/install-targets.test.js 中的断言(如检查 .cursor/rules/common-coding-style.mdcommon-agents.md 等文件)验证了安装清单对这些规则文件的覆盖,并处理了“平台规则与原生 .cursor/rules 内容冲突时优先保留原生内容”的逻辑。

换言之,当开发者通过 ECC 的安装流程把 harness 部署到一个 Kotlin/Ktor 项目时,kotlin-patterns.md 会随规则族落入目标项目的 .cursor/rules/,此后 Cursor 中的 Agent 在触碰任何 .kt 文件时即自动获得本文第二节至第五节的全部约束——无需在每条提示中手动复述团队约定。

九、速查与落地建议

结合规则文件本体与 skill 的完整版,可提炼出 Kotlin 项目的最小约束集:

约束 落地方式 依据
穷举类型层级 sealed class/interface + 穷举 when,失败态用 Nothing kotlin-patterns.md
扩展函数私有化 领域扩展声明为类内 private 成员函数 SKILL.md Extension Functions 节
作用域函数不嵌套 优先 ?. 链,let/apply/also/run/with 按语义选择 SKILL.md Scope Functions 节
DI 构造器注入 KMP 用 Koin(single/factory 区分生命周期),Android 用 Hilt rules/kotlin/patterns.md
协程测试 runTest + Kotest + MockK,Kover 出覆盖率 kotlin-testing.md
构建脚本同受约束 globs 覆盖 build.gradle.kts,detekt 静态检查 SKILL.md Gradle Kotlin DSL 节

最后需要说明适用前提:本文所有版本与依赖坐标(Kotlin 2.3.10、Ktor 3.4.0、Koin 4.2.0 等)均以当前仓库 skill 文档中记录的值为准;规则文件本身只声明约束语义,不绑定具体依赖版本,实际项目中应按自身技术栈调整,但“common 打底、语言规则扩展、skill 承接深度”这一三层结构可以直接照搬到其他语言的规则体系建设中。

登录后查看全文
热门项目推荐
相关项目推荐