kotlin-quality

v2026.09.24

Kotlin code quality toolchain — detekt (static analysis with rule sets, baseline, custom rules), ktlint (formatter + linter for the Kotlin style guide), and Compose-specific lint rules. Covers Gradle integration (KMP-aware), pre-commit hooks, CI integration, baseline files for legacy code, and config patterns for production apps including Jetpack Compose specifics (detekt-compose-rules). USE WHEN: user mentions "detekt", "ktlint", "ktlint-gradle", "detekt-gradle", "kotlin static analysis", "kotlin formatter", "kotlin lint", "compose-rules detekt", "kotlin code style", "detekt baseline" DO NOT USE FOR: Java-only static analysis - use SpotBugs/Checkstyle skill DO NOT USE FOR: Rust code quality - use `quality/rust-supply-chain` DO NOT USE FOR: Security-specific Kotlin issues - use `security/kotlin-security` DO NOT USE FOR: TypeScript linting - use eslint-specific skill

GitHub
安装命令
npx skhub add claude-dev-suite/kotlin-quality
Markdown
SKILL.md

Kotlin Quality — detekt + ktlint

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: detekt or ktlint.

Tool Comparison

ToolPurpose
ktlintFormatter + minimal linter — enforces Kotlin official style. Auto-fixes most issues.
detektComprehensive static analyzer — code smells, complexity, security, naming, magic numbers, performance, style. Highly configurable.
Compose Rules (Twitter/Slack/Mrtn)Compose-specific lint rules — composable naming, side-effects, parameter ordering. Plugs into both detekt and ktlint.
Android LintAndroid-specific (resources, manifest, lifecycle). Run alongside, not replaced.

Use both ktlint and detekt — complementary. ktlint for formatting, detekt for everything else.

Setup — ktlint (Gradle Plugin)

// build.gradle.kts (root)
plugins {
    id("org.jlleitschuh.gradle.ktlint") version "12.1.1" apply false
}

subprojects {
    apply(plugin = "org.jlleitschuh.gradle.ktlint")

    configure<org.jlleitschuh.gradle.ktlint.KtlintExtension> {
        version.set("1.4.1")
        verbose.set(true)
        outputToConsole.set(true)
        coloredOutput.set(true)
        ignoreFailures.set(false)
        enableExperimentalRules.set(false)
        filter {
            exclude("**/generated/**")
            exclude("**/build/**")
            include("**/kotlin/**")
        }
    }
}
./gradlew ktlintCheck                              # check all modules
./gradlew ktlintFormat                              # auto-fix
./gradlew :shared:ktlintCheck                       # one module

ktlint Configuration via .editorconfig

# .editorconfig
root = true

[*.{kt,kts}]
indent_size = 4
max_line_length = 120
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^

# ktlint-specific
ktlint_standard = enabled
ktlint_experimental = disabled
ktlint_function_naming_ignore_when_annotated_with = Composable,Test
ktlint_compose = enabled

Setup — detekt

// build.gradle.kts (root)
plugins {
    id("io.gitlab.arturbosch.detekt") version "1.23.7" apply false
}

subprojects {
    apply(plugin = "io.gitlab.arturbosch.detekt")

    configure<io.gitlab.arturbosch.detekt.extensions.DetektExtension> {
        config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
        baseline = file("$projectDir/detekt-baseline.xml")
        buildUponDefaultConfig = true
        autoCorrect = false                          // CI-safe
        parallel = true
    }

    dependencies {
        "detektPlugins"("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.7")
        "detektPlugins"("io.nlopez.compose.rules:detekt:0.4.16")    // Compose rules
    }
}

tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
    reports {
        html.required.set(true)
        xml.required.set(true)
        sarif.required.set(true)
    }
}
./gradlew detekt
./gradlew detektBaseline                              # generate baseline
./gradlew :shared:detekt

detekt.yml — Production Config

# config/detekt/detekt.yml
build:
  maxIssues: 0
  weights:

complexity:
  CyclomaticComplexMethod:
    threshold: 15
  LongMethod:
    threshold: 60
  LongParameterList:
    functionThreshold: 8
  TooManyFunctions:
    thresholdInClasses: 30
    ignoreAnnotatedFunctions:
      - 'Composable'

empty-blocks:
  active: true

exceptions:
  TooGenericExceptionCaught:
    exceptionNames:
      - 'Exception'
      - 'RuntimeException'
      - 'Throwable'
    allowedExceptionNameRegex: '_|(ignore|expected).*'

naming:
  FunctionNaming:
    functionPattern: '[a-z][a-zA-Z0-9]*'
    excludes: ['**/test/**', '**/androidTest/**']
    ignoreAnnotated:
      - 'Composable'                                  # PascalCase composables OK

performance:
  active: true

potential-bugs:
  HasPlatformType:
    active: true                                       # KMP-friendly: catch missing nullability

style:
  MagicNumber:
    ignoreNumbers: ['-1', '0', '1', '2', '100', '1000']
    ignoreEnums: true
  MaxLineLength:
    maxLineLength: 120
  ReturnCount:
    max: 4
  WildcardImport:
    active: true

formatting:                                            # ktlint-formatting plugin
  active: true
  android: false
  autoCorrect: false

Baseline (For Legacy Code)

./gradlew detektBaseline    # generates detekt-baseline.xml — commit it

New issues fail CI; existing ones silently allowed. Reduce baseline over time.

Compose Rules

Twitter/Slack/Mrtn maintain Compose-specific lint rules:

dependencies {
    "detektPlugins"("io.nlopez.compose.rules:detekt:0.4.16")
    // OR for ktlint
    "ktlintRuleset"("io.nlopez.compose.rules:ktlint:0.4.16")
}

Catches:

  • @Composable not in PascalCase
  • Modifier parameter not first
  • remember { mutableStateOf() } instead of by remember
  • Side effects outside LaunchedEffect
  • Missing key in LazyColumn.items
  • Composable returning unit named like accessor

CI Integration — GitHub Actions

# .github/workflows/quality.yml
name: Kotlin Quality

on: [push, pull_request]

jobs:
  ktlint-detekt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { java-version: '17', distribution: 'temurin' }
      - uses: gradle/actions/setup-gradle@v4

      - name: ktlint
        run: ./gradlew ktlintCheck

      - name: detekt
        run: ./gradlew detekt

      - name: Upload SARIF (GitHub Code Scanning)
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: build/reports/detekt/detekt.sarif

      - name: Upload reports on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: quality-reports
          path: |
            **/build/reports/ktlint/
            **/build/reports/detekt/

Pre-Commit Hook

# lefthook.yml
pre-commit:
  parallel: true
  commands:
    ktlint:
      glob: "*.{kt,kts}"
      run: ./gradlew ktlintFormat -PfilesToFormat={staged_files}
      stage_fixed: true
    detekt:
      glob: "*.{kt,kts}"
      run: ./gradlew detekt

Custom detekt Rules

class NoPrintStatement : Rule() {
    override val issue = Issue(
        id = "NoPrintStatement",
        severity = Severity.Style,
        description = "Use Logger.d() instead of println()",
        debt = Debt.FIVE_MINS,
    )

    override fun visitCallExpression(expression: KtCallExpression) {
        super.visitCallExpression(expression)
        val name = expression.calleeExpression?.text
        if (name in setOf("println", "print")) {
            report(CodeSmell(issue, Entity.from(expression), "Use a logger"))
        }
    }
}

class CustomRuleSet : RuleSetProvider {
    override val ruleSetId: String = "custom"
    override fun instance(config: Config) = RuleSet(ruleSetId, listOf(NoPrintStatement()))
}

Register via META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider.

Wallet App Quality Checklist

For BHODL-style production:

  • ktlintCheck and detekt in CI
  • No baseline regressions (new issues block PR)
  • Compose rules enabled (consistent composable patterns)
  • No println / Log.d (use Kermit / Timber)
  • No TODO/FIXME in main code (use issue tracker)
  • Magic numbers explained or named constants
  • Cyclomatic complexity ≤ 15 per function
  • No @Suppress without comment explaining why
  • All public types documented (KDoc)
  • No platform types from Java interop without explicit nullability
  • No !! force-unwrap in main code (review usage)

detekt Rule Sets Cheat Sheet

Rule setHighlights
complexityCyclomaticComplexMethod, LongMethod, LongParameterList, NestedBlockDepth
coroutinesRedundantSuspendModifier, GlobalCoroutineUsage
empty-blocksEmptyFunctionBlock, EmptyCatchBlock
exceptionsTooGenericExceptionCaught, SwallowedException
namingFunctionNaming, ClassNaming, VariableNaming
performanceForEachOnRange, SpreadOperator
potential-bugsEqualsAlwaysReturnsTrueOrFalse, HasPlatformType, NullableToStringCall
styleMagicNumber, ReturnCount, ThrowsCount, WildcardImport
formatting (via plugin)All ktlint rules as detekt issues

Anti-Patterns

Anti-patternWhy it's badCorrect approach
Skipping ktlint/detekt in CIStyle/quality driftsBoth in CI on every PR
autoCorrect = true in CIMutates code unexpectedlySet false in CI; true only for pre-commit
Massive baseline never reducedQuality debt accumulatesReduce baseline by ≥1 issue per PR rule
Hardcoded suppressions everywhereDefeats lintUse baseline for legacy, fix new issues
Per-developer .editorconfig overridesInconsistentSingle root .editorconfig, no exceptions
Skipping Compose rules in Compose projectMisses real bugsAlways enable Compose rules
MagicNumber rule disabled globallyMisses real magic numbersConfigure ignore list, don't disable
WildcardImport allowedIDE auto-import noiseDisable wildcard imports project-wide
TooGenericExceptionCaught disabledHides bugsAllow only with _ or ignore named param

Troubleshooting

SymptomCauseFix
ktlint: line too long but config says 120Editorconfig overrideCheck nested .editorconfig
detekt: false positive on ComposableCompose rules not loadedAdd detekt-compose-rules plugin
Slow detektMany filesparallel = true, exclude generated/build
Baseline drift between branchesAuto-regeneratedCommit baseline manually
detekt-formatting and ktlint conflictSame rules, different configPick one for each rule
KMP commonMain not analyzedSource set configApply ktlint/detekt to all source sets
Compose preview functions flagged as unused@Preview lint exception missingConfigure excludeAnnotatedFunctions: ['Preview']
MaxLineLength flagged inside string templateHard to break// ktlint-disable max-line-length or refactor
KSP-generated code flaggedExcludes wrongAdd **/generated/** to excludes

When NOT to Use This Skill

ScenarioUse Instead
Rust supply chainquality/rust-supply-chain
Generic vuln scanning across languagesquality/osv-scanner
Java SpotBugs/CheckstyleJava-specific quality skill
TypeScript ESLintTypeScript-specific
Android Lint (manifest, resources)Android Lint built-in
Kotlin security-specific issuessecurity/kotlin-security
KMP code patternsmobile/kotlin-multiplatform
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

MIT

源路径

skills/quality/kotlin-quality

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1