build:
  maxIssues: 0
  weights:
    info: 0
    hint: 0
    warning: 0
    error: 1

# ─────────────────────────────────────────────────────────────────────────────
# Custom Askimo rules
# ─────────────────────────────────────────────────────────────────────────────
askimo:
  RememberCoroutineScopeInConditionalComposable:
    active: true
    severity: error

# ─────────────────────────────────────────────────────────────────────────────
# Comments
# ─────────────────────────────────────────────────────────────────────────────
comments:
  active: true
  severity: warning        # documentation issues are conventions, not logic bugs
  AbsentOrWrongFileLicense:
    active: false          # Handled by Spotless licenseHeaderFile
  CommentOverPrivateFunction:
    active: false
  CommentOverPrivateProperty:
    active: false
  DeprecatedBlockTag:
    active: true
  EndOfSentenceFormat:
    active: false
  KDocReferencesNonPublicProperty:
    active: true
  OutdatedDocumentation:
    active: true
  UndocumentedPublicClass:
    active: false          # Too noisy for a desktop/UI codebase
  UndocumentedPublicFunction:
    active: false
  UndocumentedPublicProperty:
    active: false

# ─────────────────────────────────────────────────────────────────────────────
# Complexity
# ─────────────────────────────────────────────────────────────────────────────
complexity:
  active: true
  severity: warning        # complexity is a maintainability concern, not a correctness one
  CognitiveComplexMethod:
    active: true
    threshold: 20          # slightly relaxed for UI/state-heavy code
    ignoreAnnotated: ['Composable']   # Composable UI trees are inherently complex
  ComplexCondition:
    active: true
    threshold: 5
  ComplexInterface:
    active: false
  CyclomaticComplexMethod:
    active: true
    threshold: 15
    ignoreAnnotated: ['Composable']   # UI trees are inherently complex
  LabeledExpression:
    active: true
  LargeClass:
    active: true
    threshold: 600         # desktop files are large; Gradle build scripts excluded
    excludes: ['**/build.gradle.kts', '**/desktop/build.gradle.kts']
    ignoreAnnotated: ['Composable']   # UI files naturally grow large
  LongMethod:
    active: true
    threshold: 80
    excludes: ['**/build.gradle.kts']
    ignoreAnnotated: ['Composable']   # Composable functions are inherently long UI trees
  LongParameterList:
    active: true
    functionThreshold: 8
    constructorThreshold: 8
    ignoreDefaultParameters: true
    ignoreDataClasses: true
    ignoreAnnotated: ['Composable']   # Compose functions commonly have many params
  MethodOverloading:
    active: false
  NamedArguments:
    active: false
  NestedBlockDepth:
    active: true
    threshold: 5
  NestedScopeFunctions:
    active: true
    threshold: 3
  ReplaceSafeCallChainWithRun:
    active: true
  StringLiteralDuplication:
    active: false          # Too many false positives in UI/i18n code
  TooManyFunctions:
    active: true
    thresholdInFiles: 60
    thresholdInClasses: 30
    thresholdInObjects: 30
    thresholdInInterfaces: 15
    thresholdInEnums: 10
    ignoreDeprecated: true
    ignorePrivate: true
    ignoreAnnotated: ['Composable']
    excludes: ['**/build.gradle.kts']

# ─────────────────────────────────────────────────────────────────────────────
# Coroutines
# ─────────────────────────────────────────────────────────────────────────────
coroutines:
  active: true
  severity: error          # coroutine misuse causes cancellation leaks and race conditions
  GlobalCoroutineUsage:
    active: true           # ban GlobalScope — use structured concurrency
    severity: error
  InjectDispatcher:
    active: true           # dispatchers should be injected, not hardcoded
    severity: warning      # testability concern rather than runtime failure
    dispatcherNames: ['IO', 'Default', 'Unconfined']
  RedundantSuspendModifier:
    active: true
    severity: warning
  SleepInsteadOfDelay:
    active: true
    severity: error        # Thread.sleep blocks the coroutine thread pool
  SuspendFunSwallowedCancellation:
    active: true
    severity: error
  SuspendFunWithCoroutineScopeReceiver:
    active: true
    severity: error

# ─────────────────────────────────────────────────────────────────────────────
# Empty blocks
# ─────────────────────────────────────────────────────────────────────────────
empty-blocks:
  active: true
  severity: warning        # empty blocks are usually dead code, not runtime bugs
  EmptyCatchBlock:
    active: true
    allowedExceptionNameRegex: '_|ignored|expected'
  EmptyClassBlock:
    active: true
  EmptyDefaultConstructor:
    active: true
  EmptyDoWhileBlock:
    active: true
  EmptyElseBlock:
    active: true
  EmptyFinallyBlock:
    active: true
  EmptyForBlock:
    active: true
  EmptyFunctionBlock:
    active: true
    ignoreOverridden: true
  EmptyIfBlock:
    active: true
  EmptyInitBlock:
    active: true
  EmptyKtFile:
    active: true
  EmptySecondaryConstructor:
    active: true
  EmptyTryBlock:
    active: true
  EmptyWhenBlock:
    active: true
  EmptyWhileBlock:
    active: true

# ─────────────────────────────────────────────────────────────────────────────
# Exceptions
# ─────────────────────────────────────────────────────────────────────────────
exceptions:
  active: true
  severity: error          # silencing or mishandling exceptions hides real failures
  ExceptionRaisedInUnexpectedLocation:
    active: true
    severity: error
  InstanceOfCheckForException:
    active: true
    severity: warning
  NotImplementedDeclaration:
    active: true
    severity: warning
  ObjectExtendsThrowable:
    active: true
    severity: warning
  PrintStackTrace:
    active: true
    severity: error        # leaks stack traces to stdout instead of structured logging
  RethrowCaughtException:
    active: true
    severity: warning
  ReturnFromFinally:
    active: true
    severity: error
  SwallowedException:
    active: true
    severity: error
    excludes: ['**/test/**', '**/*Test.kt', '**/*Tests.kt', '**/*Spec.kt', '**/*TestBase.kt']
    ignoredExceptionTypes:
      - 'InterruptedException'
      - 'MalformedURLException'
      - 'NumberFormatException'
      - 'ParseException'
    allowedExceptionNameRegex: '_|ignored|expected'
  ThrowingExceptionFromFinally:
    active: true
    severity: error
  ThrowingExceptionInMain:
    active: false          # CLI main functions intentionally throw
  ThrowingExceptionsWithoutMessageOrCause:
    active: true
    severity: warning
    excludes: ['**/test/**', '**/*Test.kt', '**/*Spec.kt']
  ThrowingNewInstanceOfSameException:
    active: true
    severity: warning
  TooGenericExceptionCaught:
    active: true
    severity: warning        # masks unexpected failures
    exceptionNames:
      - 'ArrayIndexOutOfBoundsException'
      - 'Error'
      - 'Exception'
      - 'IllegalMonitorStateException'
      - 'NullPointerException'
      - 'IndexOutOfBoundsException'
      - 'RuntimeException'
      - 'Throwable'
    allowedExceptionNameRegex: '_|ignored|expected'
    excludes: ['**/test/**', '**/*Test.kt']
  TooGenericExceptionThrown:
    active: true
    severity: error
    excludes: ['**/test/**', '**/*Test.kt', '**/*Tests.kt', '**/*Spec.kt', '**/*TestBase.kt']
    exceptionNames:
      - 'Error'
      - 'Exception'
      - 'RuntimeException'
      - 'Throwable'

# ─────────────────────────────────────────────────────────────────────────────
# Naming
# ─────────────────────────────────────────────────────────────────────────────
naming:
  active: true
  severity: warning        # naming is a readability convention
  BooleanPropertyNaming:
    active: true
    allowedPattern: '^(is|has|are|can|should|was|will|use)[A-Z].*'
  ClassNaming:
    active: true
    classPattern: '[A-Z][a-zA-Z0-9]*'
  ConstructorParameterNaming:
    active: true
    parameterPattern: '[a-z][A-Za-z0-9]*'
    privateParameterPattern: '[a-z][A-Za-z0-9]*'
  EnumNaming:
    active: true
    enumEntryPattern: '[A-Z][_A-Z0-9]*'
  ForbiddenClassName:
    active: false
  FunctionMaxLength:
    active: false
  FunctionMinLength:
    active: false
  FunctionNaming:
    active: true
    functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
    excludeClassPattern: '$^'
    ignoreAnnotated: ['Composable']  # Composable functions are PascalCase by convention
  FunctionParameterNaming:
    active: true
    parameterPattern: '[a-z][A-Za-z0-9]*'
  InvalidPackageDeclaration:
    active: true
  LambdaParameterNaming:
    active: true
    parameterPattern: '[a-z][A-Za-z0-9]*|_'
  MatchingDeclarationName:
    active: true
    mustBeFirst: true
  MemberNameEqualsClassName:
    active: true
    ignoreOverridden: true
  NoNameShadowing:
    active: true
  NonBooleanPropertyPrefixedWithIs:
    active: true
  ObjectPropertyNaming:
    active: true
    constantPattern: '[A-Za-z][_A-Za-z0-9]*'
    propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
    privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
  PackageNaming:
    active: true
    packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
  TopLevelPropertyNaming:
    active: true
    constantPattern: '[A-Z][_A-Z0-9]*'
    propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
    privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
  VariableMaxLength:
    active: false
  VariableMinLength:
    active: true
    minimumVariableNameLength: 1
  VariableNaming:
    active: true
    variablePattern: '[a-z][A-Za-z0-9]*'
    privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'

# ─────────────────────────────────────────────────────────────────────────────
# Performance
# ─────────────────────────────────────────────────────────────────────────────
performance:
  active: true
  severity: warning        # performance hints are advisory
  ArrayPrimitive:
    active: true
  CouldBeSequence:
    active: true
    threshold: 3
  ForEachOnRange:
    active: true
  SpreadOperator:
    active: true
    excludes:
      - '**/test/**'
      - '**/*Test.kt'
  UnnecessaryPartOfBinaryExpression:
    active: true
  UnnecessaryTemporaryInstantiation:
    active: true

# ─────────────────────────────────────────────────────────────────────────────
# Potential bugs
# ─────────────────────────────────────────────────────────────────────────────
potential-bugs:
  active: true
  severity: error          # default: potential bugs are errors
  AvoidReferentialEquality:
    active: true
    severity: error
    forbiddenTypePatterns:
      - 'kotlin.String'
  CastToNullableType:
    active: true
    severity: error
  Deprecation:
    active: false          # Too noisy with third-party libraries
  DontDowncastCollectionTypes:
    active: true
    severity: error
  DoubleMutabilityForCollection:
    active: true
    severity: warning
  ElseCaseInsteadOfExhaustiveWhen:
    active: true
    severity: error
  EqualsAlwaysReturnsTrueOrFalse:
    active: true
    severity: error
  EqualsWithHashCodeExist:
    active: true
    severity: error
  ExitOutsideMain:
    active: true
    severity: error
    excludes: ['**/cli/**']   # CLI is allowed to call exitProcess
  HasPlatformType:
    active: true
    severity: warning
  IgnoredReturnValue:
    active: true
    severity: error
    restrictToConfig: true
    returnValueAnnotations: ['CheckResult', 'CheckReturnValue']
  ImplicitDefaultLocale:
    active: true
    severity: warning
  ImplicitUnitReturnType:
    active: true
    severity: warning
    allowExplicitReturnType: true
  InvalidRange:
    active: true
    severity: error
  IteratorHasNextCallsNextMethod:
    active: true
    severity: error
  IteratorNotThrowingNoSuchElementException:
    active: true
    severity: error
  LateinitUsage:
    active: false          # Koin inject uses lateinit
  MapGetWithNotNullAssertionOperator:
    active: true
    severity: error
  MissingPackageDeclaration:
    active: true
    severity: error
    excludes: ['**/*.kts']
  NullCheckOnMutableProperty:
    active: true
    severity: error
  NullableToStringCall:
    active: true
    severity: warning
  PropertyUsedBeforeDeclaration:
    active: true
    severity: error
  UnconditionalJumpStatementInLoop:
    active: true
    severity: error
  UnnecessaryNotNullCheck:
    active: true
    severity: warning
  UnnecessaryNotNullOperator:
    active: true
    severity: warning
  UnnecessarySafeCall:
    active: true
    severity: warning
  UnreachableCode:
    active: true
    severity: error
  UnsafeCallOnNullableType:
    active: true
    severity: error
    excludes: ['**/test/**', '**/*Test.kt']
  UnsafeCast:
    active: true
    severity: error
  UnusedUnaryOperator:
    active: true
    severity: warning
  UselessPostfixExpression:
    active: true
    severity: error
  WrongEqualsTypeParameter:
    active: true
    severity: error

# ─────────────────────────────────────────────────────────────────────────────
# Style
# ─────────────────────────────────────────────────────────────────────────────
style:
  active: true
  severity: warning        # style issues are coding-convention, not logic bugs
  AlsoCouldBeApply:
    active: true
  BracesOnIfStatements:
    active: false          # Handled by ktlint
  BracesOnWhenStatements:
    active: false
  CanBeNonNullable:
    active: true
  CascadingCallWrapping:
    active: false
  ClassOrdering:
    active: false
  CollapsibleIfStatements:
    active: true
  DataClassContainsFunctions:
    active: false
  DataClassShouldBeImmutable:
    active: true
  DestructuringDeclarationWithTooManyEntries:
    active: true
    maxDestructuringEntries: 5
  EqualsNullCall:
    active: true
    severity: error        # equalsNullCall is a correctness bug (obj.equals(null) vs obj == null)
  EqualsOnSignatureLine:
    active: false
  ExplicitCollectionElementAccessMethod:
    active: true
  ExplicitItLambdaParameter:
    active: true
  ExpressionBodySyntax:
    active: false          # matter of taste; don't enforce
  ForbiddenAnnotation:
    active: false
  ForbiddenComment:
    active: true
    comments:
      - reason: 'Use a GitHub issue instead of a FIXME comment.'
        value: 'FIXME:'
      - reason: 'STOPSHIP comments must be resolved before release.'
        value: 'STOPSHIP:'
    allowedPatterns: ''
  ForbiddenImport:
    active: true
    severity: error        # banned imports violate architectural constraints
    excludes: ['**/test/**', '**/*Test.kt', '**/*Tests.kt', '**/*Spec.kt', '**/*TestBase.kt']
    imports:
      - reason: 'Use kotlinx.coroutines.GlobalScope alternatives with structured concurrency.'
        value: 'kotlinx.coroutines.GlobalScope'
      - reason: 'Use kotlin.io.println only in CLI main; avoid in library/UI code.'
        value: 'java.io.PrintStream'
  ForbiddenMethodCall:
    active: false
  ForbiddenSuppress:
    active: false
  ForbiddenVoid:
    active: true
    ignoreOverridden: true
    ignoreUsageInGenerics: true
  FunctionOnlyReturningConstant:
    active: true
  LoopWithTooManyJumpStatements:
    active: true
    maxJumpCount: 2
  MagicNumber:
    active: false
    ignoreNumbers: ['-1', '0', '1', '2', '100', '1000']
    ignoreHashCodeFunction: true
    ignorePropertyDeclaration: true
    ignoreLocalVariableDeclaration: false
    ignoreConstantDeclaration: true
    ignoreCompanionObjectPropertyDeclaration: true
    ignoreAnnotation: true
    ignoreNamedArgument: true
    ignoreEnums: true
    ignoreRanges: true
    ignoreExtensionFunctions: true
  MandatoryBracesLoops:
    active: false
  MaxChainedCallsOnSameLine:
    active: false
  MaxLineLength:
    active: false          # Handled by ktlint / .editorconfig
  MayBeConst:
    active: true
  ModifierOrder:
    active: true
  MultilineLambdaItParameter:
    active: true
  MultilineRawStringIndentation:
    active: false
  NestedClassesVisibility:
    active: true
  NewLineAtEndOfFile:
    active: false          # Handled by Spotless
  NoTabs:
    active: false          # Handled by Spotless
  NullableBooleanCheck:
    active: true
    severity: error        # nullable boolean checks are subtle correctness bugs
  ObjectLiteralToLambda:
    active: true
  OptionalAbstractKeyword:
    active: true
  OptionalUnit:
    active: false
  PreferToOverPairSyntax:
    active: true
  ProtectedMemberInFinalClass:
    active: true
  RedundantExplicitType:
    active: false
  RedundantHigherOrderMapUsage:
    active: true
  RedundantVisibilityModifierRule:
    active: false
  ReturnCount:
    active: true
    max: 4
    excludedFunctions: ['equals']
    excludeLabeled: false
    excludeReturnFromLambda: true
    excludeGuardClauses: true
  SafeCast:
    active: true
  SerialVersionUIDInSerializableClass:
    active: false          # Using kotlinx.serialization, not Java Serializable
  SpacingBetweenPackageAndImports:
    active: false          # Handled by ktlint
  StringShouldBeRawString:
    active: false
  ThrowsCount:
    active: true
    max: 3
    excludeGuardClauses: true
  TrailingWhitespace:
    active: false          # Handled by Spotless
  TrimMultilineRawString:
    active: true
  UnderscoresInNumericLiterals:
    active: true
    acceptableLength: 5
    allowNonStandardGrouping: false
  UnnecessaryAbstractClass:
    active: true
  UnnecessaryAnnotationUseSiteTarget:
    active: true
  UnnecessaryApply:
    active: true
  UnnecessaryBackticks:
    active: true
  UnnecessaryBracesAroundTrailingLambda:
    active: true
  UnnecessaryFilter:
    active: true
  UnnecessaryInheritance:
    active: true
  UnnecessaryInnerClass:
    active: true
  UnnecessaryLet:
    active: true
  UnnecessaryParentheses:
    active: true
  UntilInsteadOfRangeTo:
    active: true
  UnusedImports:
    active: false          # ktlint handles this
  UnusedParameter:
    active: true
    allowedNames: '_|ignored|expected'
  UnusedPrivateClass:
    active: true
  UnusedPrivateMember:
    active: true
    allowedNames: '_|ignored|expected'
  UnusedPrivateProperty:
    active: true
    allowedNames: '_|ignored|expected'
  UseAnyOrNoneInsteadOfFind:
    active: true
  UseArrayLiteralsInAnnotations:
    active: true
  UseCheckNotNull:
    active: true
    severity: error        # incorrect null handling is a logic bug
    excludes: ['**/test/**', '**/*Test.kt', '**/*Tests.kt', '**/*Spec.kt', '**/*TestBase.kt']
  UseCheckOrError:
    active: true
    severity: error
    excludes: ['**/test/**', '**/*Test.kt', '**/*Tests.kt', '**/*Spec.kt', '**/*TestBase.kt']
  UseDataClass:
    active: false          # too aggressive for domain model classes with logic
  UseEmptyCounterpart:
    active: true
  UseIfEmptyOrIfBlank:
    active: true
  UseIfInsteadOfWhen:
    active: true
    ignoreWhenContainingVariableDeclaration: true
  UseIsNullOrEmpty:
    active: true
  UseLet:
    active: false
  UseOrEmpty:
    active: true
  UseRequire:
    active: true
    severity: error
    excludes: ['**/test/**', '**/*Test.kt', '**/*Tests.kt', '**/*Spec.kt', '**/*TestBase.kt']
  UseRequireNotNull:
    active: true
    severity: error
    excludes: ['**/test/**', '**/*Test.kt', '**/*Tests.kt', '**/*Spec.kt', '**/*TestBase.kt']
  UseSumOfInsteadOfFlatMapSize:
    active: true
  VarCouldBeVal:
    active: true
    severity: error        # unnecessary mutability can cause unintended state changes
    ignoreLateinitVar: true
  WildcardImport:
    active: true
    excludeImports:
      - 'java.util.*'
      - 'kotlinx.android.synthetic.*'
      - 'androidx.compose.*'
