@coralai/sps-cli 0.41.2 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/README.md +34 -3
  2. package/dist/commands/cardAdd.d.ts +1 -1
  3. package/dist/commands/cardAdd.d.ts.map +1 -1
  4. package/dist/commands/cardAdd.js +16 -6
  5. package/dist/commands/cardAdd.js.map +1 -1
  6. package/dist/commands/cardDashboard.js +1 -1
  7. package/dist/commands/cardDashboard.js.map +1 -1
  8. package/dist/commands/doctor.d.ts +9 -0
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/doctor.js +3 -314
  11. package/dist/commands/doctor.js.map +1 -1
  12. package/dist/commands/hookCommand.d.ts.map +1 -1
  13. package/dist/commands/hookCommand.js +6 -7
  14. package/dist/commands/hookCommand.js.map +1 -1
  15. package/dist/commands/pmCommand.js +1 -1
  16. package/dist/commands/pmCommand.js.map +1 -1
  17. package/dist/commands/projectInit.d.ts.map +1 -1
  18. package/dist/commands/projectInit.js +60 -37
  19. package/dist/commands/projectInit.js.map +1 -1
  20. package/dist/commands/setup.d.ts.map +1 -1
  21. package/dist/commands/setup.js +3 -30
  22. package/dist/commands/setup.js.map +1 -1
  23. package/dist/commands/skillCommand.d.ts +2 -0
  24. package/dist/commands/skillCommand.d.ts.map +1 -0
  25. package/dist/commands/skillCommand.js +235 -0
  26. package/dist/commands/skillCommand.js.map +1 -0
  27. package/dist/commands/tick.js +1 -1
  28. package/dist/commands/tick.js.map +1 -1
  29. package/dist/core/checklist.d.ts +22 -0
  30. package/dist/core/checklist.d.ts.map +1 -0
  31. package/dist/core/checklist.js +38 -0
  32. package/dist/core/checklist.js.map +1 -0
  33. package/dist/core/checklist.test.d.ts +2 -0
  34. package/dist/core/checklist.test.d.ts.map +1 -0
  35. package/dist/core/checklist.test.js +74 -0
  36. package/dist/core/checklist.test.js.map +1 -0
  37. package/dist/core/config.d.ts +1 -1
  38. package/dist/core/config.d.ts.map +1 -1
  39. package/dist/core/config.js +1 -1
  40. package/dist/core/config.js.map +1 -1
  41. package/dist/core/config.test.js +7 -4
  42. package/dist/core/config.test.js.map +1 -1
  43. package/dist/core/context.d.ts +1 -1
  44. package/dist/core/context.d.ts.map +1 -1
  45. package/dist/core/skillStore.d.ts +46 -0
  46. package/dist/core/skillStore.d.ts.map +1 -0
  47. package/dist/core/skillStore.js +197 -0
  48. package/dist/core/skillStore.js.map +1 -0
  49. package/dist/core/skillStore.test.d.ts +2 -0
  50. package/dist/core/skillStore.test.d.ts.map +1 -0
  51. package/dist/core/skillStore.test.js +190 -0
  52. package/dist/core/skillStore.test.js.map +1 -0
  53. package/dist/engines/EventHandler.test.js +3 -3
  54. package/dist/engines/EventHandler.test.js.map +1 -1
  55. package/dist/engines/MonitorEngine.js +2 -2
  56. package/dist/engines/MonitorEngine.js.map +1 -1
  57. package/dist/engines/SchedulerEngine.js +1 -1
  58. package/dist/engines/SchedulerEngine.js.map +1 -1
  59. package/dist/engines/StageEngine.js +3 -3
  60. package/dist/engines/StageEngine.js.map +1 -1
  61. package/dist/engines/engine-pipeline-adapter.test.js +2 -2
  62. package/dist/engines/engine-pipeline-adapter.test.js.map +1 -1
  63. package/dist/interfaces/TaskBackend.d.ts +3 -1
  64. package/dist/interfaces/TaskBackend.d.ts.map +1 -1
  65. package/dist/main.js +19 -17
  66. package/dist/main.js.map +1 -1
  67. package/dist/models/types.d.ts +16 -1
  68. package/dist/models/types.d.ts.map +1 -1
  69. package/dist/providers/MarkdownTaskBackend.d.ts +2 -1
  70. package/dist/providers/MarkdownTaskBackend.d.ts.map +1 -1
  71. package/dist/providers/MarkdownTaskBackend.js +28 -5
  72. package/dist/providers/MarkdownTaskBackend.js.map +1 -1
  73. package/dist/providers/registry.d.ts.map +1 -1
  74. package/dist/providers/registry.js +5 -7
  75. package/dist/providers/registry.js.map +1 -1
  76. package/package.json +1 -1
  77. package/project-template/.claude/hooks/start.sh +44 -0
  78. package/project-template/.claude/settings.json +1 -1
  79. package/skills/architecture-decision-records/SKILL.md +207 -0
  80. package/skills/backend/SKILL.md +62 -0
  81. package/skills/backend/references/api-design.md +168 -0
  82. package/skills/backend/references/caching.md +181 -0
  83. package/skills/backend/references/data-access.md +173 -0
  84. package/skills/backend/references/layering.md +181 -0
  85. package/skills/backend/references/observability.md +190 -0
  86. package/skills/backend/references/resilience.md +201 -0
  87. package/skills/backend/references/security.md +186 -0
  88. package/skills/backend-architect/SKILL.md +119 -0
  89. package/skills/code-reviewer/SKILL.md +143 -0
  90. package/skills/coding-standards/SKILL.md +60 -0
  91. package/skills/coding-standards/references/clean-code.md +258 -0
  92. package/skills/coding-standards/references/code-review.md +192 -0
  93. package/skills/coding-standards/references/commits-and-prs.md +226 -0
  94. package/skills/coding-standards/references/error-strategy.md +193 -0
  95. package/skills/coding-standards/references/naming.md +185 -0
  96. package/skills/coding-standards/references/tdd.md +171 -0
  97. package/skills/database/SKILL.md +53 -0
  98. package/skills/database/references/indexing.md +190 -0
  99. package/skills/database/references/migrations.md +199 -0
  100. package/skills/database/references/nosql.md +185 -0
  101. package/skills/database/references/queries.md +295 -0
  102. package/skills/database/references/scaling.md +203 -0
  103. package/skills/database/references/schema.md +191 -0
  104. package/skills/database-optimizer/SKILL.md +168 -0
  105. package/skills/debugging-workflow/SKILL.md +244 -0
  106. package/skills/devops/SKILL.md +55 -0
  107. package/skills/devops/references/ci-cd.md +204 -0
  108. package/skills/devops/references/containers.md +272 -0
  109. package/skills/devops/references/deploy.md +201 -0
  110. package/skills/devops/references/iac.md +252 -0
  111. package/skills/devops/references/observability.md +228 -0
  112. package/skills/devops/references/secrets.md +178 -0
  113. package/skills/devops-automator/SKILL.md +164 -0
  114. package/skills/frontend/SKILL.md +52 -0
  115. package/skills/frontend/references/accessibility.md +222 -0
  116. package/skills/frontend/references/components.md +206 -0
  117. package/skills/frontend/references/performance.md +219 -0
  118. package/skills/frontend/references/routing.md +209 -0
  119. package/skills/frontend/references/state.md +190 -0
  120. package/skills/frontend/references/testing.md +216 -0
  121. package/skills/frontend-developer/SKILL.md +115 -0
  122. package/skills/git-workflow/SKILL.md +355 -0
  123. package/skills/golang/SKILL.md +49 -0
  124. package/skills/golang/references/concurrency.md +284 -0
  125. package/skills/golang/references/errors.md +241 -0
  126. package/skills/golang/references/idioms.md +285 -0
  127. package/skills/golang/references/testing.md +238 -0
  128. package/skills/java/SKILL.md +50 -0
  129. package/skills/java/references/concurrency.md +194 -0
  130. package/skills/java/references/idioms.md +283 -0
  131. package/skills/java/references/testing.md +228 -0
  132. package/skills/kotlin/SKILL.md +47 -0
  133. package/skills/kotlin/references/coroutines.md +240 -0
  134. package/skills/kotlin/references/idioms.md +268 -0
  135. package/skills/kotlin/references/testing.md +219 -0
  136. package/skills/mobile/SKILL.md +50 -0
  137. package/skills/mobile/references/architecture.md +204 -0
  138. package/skills/mobile/references/navigation.md +158 -0
  139. package/skills/mobile/references/performance.md +152 -0
  140. package/skills/mobile/references/platform.md +166 -0
  141. package/skills/mobile/references/state-and-data.md +174 -0
  142. package/skills/python/SKILL.md +51 -0
  143. package/skills/python/THIRD_PARTY.md +14 -0
  144. package/skills/python/references/async.md +218 -0
  145. package/skills/python/references/error-handling.md +254 -0
  146. package/skills/python/references/idioms.md +279 -0
  147. package/skills/python/references/packaging.md +233 -0
  148. package/skills/python/references/testing.md +269 -0
  149. package/skills/python/references/typing.md +292 -0
  150. package/skills/qa-tester/SKILL.md +186 -0
  151. package/skills/rust/SKILL.md +50 -0
  152. package/skills/rust/references/async.md +224 -0
  153. package/skills/rust/references/errors.md +240 -0
  154. package/skills/rust/references/ownership.md +263 -0
  155. package/skills/rust/references/testing.md +274 -0
  156. package/skills/rust/references/traits.md +250 -0
  157. package/skills/security-engineer/SKILL.md +157 -0
  158. package/skills/swift/SKILL.md +48 -0
  159. package/skills/swift/references/concurrency.md +280 -0
  160. package/skills/swift/references/idioms.md +334 -0
  161. package/skills/swift/references/testing.md +229 -0
  162. package/skills/typescript/SKILL.md +51 -0
  163. package/skills/typescript/references/async.md +241 -0
  164. package/skills/typescript/references/errors.md +208 -0
  165. package/skills/typescript/references/idioms.md +246 -0
  166. package/skills/typescript/references/testing.md +225 -0
  167. package/skills/typescript/references/tooling.md +208 -0
  168. package/skills/typescript/references/types.md +259 -0
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: kotlin
3
+ description: Kotlin language skill — idioms, null safety, coroutines, testing. Covers JVM backend and Android. Pair with `backend` / `mobile` end skills and `coding-standards` for cross-language principles.
4
+ origin: original
5
+ ---
6
+
7
+ # Kotlin
8
+
9
+ Null safety, coroutines, expressive stdlib. Used for JVM backend (Ktor, Spring) and Android. **Language-focused**.
10
+
11
+ ## When to load
12
+
13
+ - Project primary language is Kotlin (JVM / Android / Multiplatform)
14
+ - Reviewing Kotlin code
15
+ - Coroutines / Flow / async design
16
+ - Interop with Java (calling Java APIs, exposing Kotlin to Java)
17
+
18
+ ## Core principles
19
+
20
+ 1. **Nullability in the type system.** `String?` and `String` are different types; a `NullPointerException` in pure Kotlin code is almost always a mistake.
21
+ 2. **`val` by default, `var` only when mutation is required.**
22
+ 3. **Data classes for value objects.** `copy`, `equals`, `hashCode`, `toString` for free.
23
+ 4. **Prefer expressions over statements.** `if`, `when`, `try` all return values.
24
+ 5. **Sealed hierarchies over type codes.** Use `sealed class` / `sealed interface` + `when` with exhaustiveness.
25
+ 6. **Coroutines for async.** Never `Thread.sleep` or `.get()` on a `Future` in a coroutine.
26
+ 7. **No Java collections when Kotlin collections exist.** `List<T>` (Kotlin) is read-only; `MutableList<T>` is mutable.
27
+ 8. **`let` / `run` / `apply` / `also` / `with` — know the difference, don't chain all five.**
28
+
29
+ ## How to use references
30
+
31
+ | Reference | When to load |
32
+ |---|---|
33
+ | [`references/idioms.md`](references/idioms.md) | Data classes, sealed classes, scope functions, extension fns, null safety |
34
+ | [`references/coroutines.md`](references/coroutines.md) | `suspend`, `CoroutineScope`, structured concurrency, `Flow`, dispatchers |
35
+ | [`references/testing.md`](references/testing.md) | JUnit 5, Kotest, MockK, turbine for Flow |
36
+
37
+ ## Forbidden patterns (auto-reject)
38
+
39
+ - `!!` (force-unwrap) without a comment justifying it's definitely non-null
40
+ - `lateinit var` for anything that isn't initialized by a framework (DI, test setup)
41
+ - `runBlocking` in library or backend request-path code
42
+ - `GlobalScope` — always use a structured `CoroutineScope`
43
+ - `Thread.sleep` inside a `suspend` function
44
+ - `println` for logging in production code
45
+ - Returning Kotlin `List<T>` from an API that Java callers will treat as mutable (or vice versa)
46
+ - `Object` singletons holding mutable state
47
+ - `runCatching { }.getOrNull()` swallowing errors silently
@@ -0,0 +1,240 @@
1
+ # Kotlin — Coroutines
2
+
3
+ `suspend`, scopes, dispatchers, `Flow`, cancellation. Structured concurrency is the model.
4
+
5
+ ## `suspend` functions
6
+
7
+ A `suspend fun` is a function that can pause (free its thread) and resume later.
8
+
9
+ ```kotlin
10
+ suspend fun fetchUser(id: String): User {
11
+ val raw = httpClient.get("/users/$id")
12
+ return User.fromJson(raw)
13
+ }
14
+ ```
15
+
16
+ Call from another `suspend` function or from a `CoroutineScope.launch { ... }`. You can't call them from a regular function without `runBlocking` (for main / tests only).
17
+
18
+ ## Structured concurrency
19
+
20
+ Every coroutine runs in a `CoroutineScope`. When the scope is cancelled, all its children are cancelled. No orphan work.
21
+
22
+ ```kotlin
23
+ coroutineScope {
24
+ val user = async { fetchUser(id) }
25
+ val prefs = async { fetchPrefs(id) }
26
+ render(user.await(), prefs.await())
27
+ // if either throws, the other is cancelled and the exception bubbles
28
+ }
29
+ ```
30
+
31
+ Rule: **no `GlobalScope.launch`, no bare `launch` at top level.** Attach every coroutine to a scope with a known lifecycle.
32
+
33
+ ## Dispatchers
34
+
35
+ | Dispatcher | Use |
36
+ |---|---|
37
+ | `Dispatchers.Default` | CPU-bound work (limited to # of cores) |
38
+ | `Dispatchers.IO` | Blocking I/O (file, network, blocking JDBC) |
39
+ | `Dispatchers.Main` | UI thread (Android, JavaFX) |
40
+ | `Dispatchers.Unconfined` | Rarely needed; tests and hacks |
41
+
42
+ ```kotlin
43
+ suspend fun readFile(path: String): String =
44
+ withContext(Dispatchers.IO) { File(path).readText() }
45
+ ```
46
+
47
+ Only switch dispatchers at boundaries. Don't spray `withContext(IO)` inside tight logic.
48
+
49
+ ## `coroutineScope` vs. `supervisorScope`
50
+
51
+ ```kotlin
52
+ // coroutineScope: one failure cancels siblings (default)
53
+ coroutineScope {
54
+ launch { critical1() }
55
+ launch { critical2() }
56
+ }
57
+
58
+ // supervisorScope: failures isolated
59
+ supervisorScope {
60
+ launch { optional1() } // if this fails, optional2 still runs
61
+ launch { optional2() }
62
+ }
63
+ ```
64
+
65
+ Default to `coroutineScope`. Use `supervisorScope` for independent tasks where one failure shouldn't kill the rest (e.g., background refreshes).
66
+
67
+ ## Cancellation
68
+
69
+ Cancellation is cooperative. A coroutine is cancelled when:
70
+ - Its scope is cancelled.
71
+ - A structured sibling throws (in `coroutineScope`).
72
+ - `cancel()` is called on its `Job`.
73
+
74
+ `suspend` functions check cancellation at suspension points. CPU-heavy loops must opt in:
75
+
76
+ ```kotlin
77
+ repeat(1_000_000) {
78
+ doWork()
79
+ yield() // or ensureActive()
80
+ }
81
+ ```
82
+
83
+ Handle `CancellationException`:
84
+ ```kotlin
85
+ try {
86
+ doWork()
87
+ } catch (e: CancellationException) {
88
+ // clean up, then RETHROW — don't swallow
89
+ throw e
90
+ } catch (e: Exception) {
91
+ log.error("failed", e)
92
+ }
93
+ ```
94
+
95
+ **Swallowing `CancellationException` breaks the scope contract.** Always re-throw.
96
+
97
+ ## `async` / `await`
98
+
99
+ For parallel computations that return values.
100
+
101
+ ```kotlin
102
+ val (u, o) = coroutineScope {
103
+ val userD = async { getUser(id) }
104
+ val ordsD = async { getOrders(id) }
105
+ userD.await() to ordsD.await()
106
+ }
107
+ ```
108
+
109
+ Don't use `async { ... }.await()` back-to-back when you could just call the `suspend` function. That's sequential work dressed up as parallel.
110
+
111
+ ## Timeouts
112
+
113
+ ```kotlin
114
+ withTimeout(5.seconds) {
115
+ slowOp()
116
+ } // throws TimeoutCancellationException on timeout
117
+
118
+ withTimeoutOrNull(5.seconds) {
119
+ slowOp()
120
+ } // returns null on timeout
121
+ ```
122
+
123
+ ## `Flow` — asynchronous streams
124
+
125
+ `Flow` is a cold async sequence. It doesn't run until collected.
126
+
127
+ ```kotlin
128
+ fun tick(interval: Duration): Flow<Int> = flow {
129
+ var i = 0
130
+ while (currentCoroutineContext().isActive) {
131
+ emit(i++)
132
+ delay(interval)
133
+ }
134
+ }
135
+
136
+ scope.launch {
137
+ tick(1.seconds)
138
+ .map { it * 2 }
139
+ .filter { it > 10 }
140
+ .take(5)
141
+ .collect { println(it) }
142
+ }
143
+ ```
144
+
145
+ Operators (`map`, `filter`, etc.) return new Flows; `collect` is the terminal.
146
+
147
+ ### Cold vs. hot
148
+
149
+ - **Cold** (`flow { }`, `flowOf`): starts fresh per collector.
150
+ - **Hot** (`SharedFlow`, `StateFlow`): shared; values emitted regardless of collectors.
151
+
152
+ Use `StateFlow` for "current value of X" (UI state). Use `SharedFlow` for events (broadcast). Use plain `Flow` for request/response.
153
+
154
+ ### Backpressure
155
+
156
+ ```kotlin
157
+ flow.buffer(100) // buffered producer
158
+ flow.conflate() // keep only latest
159
+ flow.collectLatest { ... } // cancel prior collector when new value arrives
160
+ ```
161
+
162
+ ## Channels
163
+
164
+ One-shot or producer/consumer.
165
+
166
+ ```kotlin
167
+ val channel = Channel<Job>(capacity = 100)
168
+ launch {
169
+ for (job in channel) { process(job) }
170
+ }
171
+ channel.send(job)
172
+ channel.close()
173
+ ```
174
+
175
+ For most use cases, `Flow` is enough. Use `Channel` when you need direct send/receive semantics.
176
+
177
+ ## Exception handling
178
+
179
+ Exceptions propagate up the structured-concurrency tree. The parent coroutine sees them at `.await()` or when children complete.
180
+
181
+ ```kotlin
182
+ try {
183
+ coroutineScope {
184
+ launch { throwSomething() }
185
+ }
186
+ } catch (e: SomeError) {
187
+ // caught here
188
+ }
189
+ ```
190
+
191
+ Install a `CoroutineExceptionHandler` on a `CoroutineScope` for top-level handlers (Android ViewModel, server main). Don't scatter try/catch inside coroutines.
192
+
193
+ ## `runBlocking` — only for `main` and tests
194
+
195
+ Bridges blocking and suspending worlds. Inside a request handler or a library, `runBlocking` deadlocks threads. Don't.
196
+
197
+ ```kotlin
198
+ fun main() = runBlocking {
199
+ myApp()
200
+ }
201
+
202
+ @Test fun test() = runBlocking {
203
+ assertEquals(5, add(2, 3))
204
+ }
205
+ ```
206
+
207
+ For tests, prefer `runTest` (from `kotlinx-coroutines-test`) — it virtualizes time.
208
+
209
+ ## `runTest` — deterministic async tests
210
+
211
+ ```kotlin
212
+ @Test
213
+ fun fetches() = runTest {
214
+ val result = fetchUser("u1") // no real delay; virtual scheduler
215
+ assertEquals("u1", result.id)
216
+ advanceTimeBy(5.seconds) // control time
217
+ }
218
+ ```
219
+
220
+ ## Android-specific coroutine scopes
221
+
222
+ - `viewModelScope` — cancels on `ViewModel.onCleared()`
223
+ - `lifecycleScope` — cancels on lifecycle destroy
224
+ - `repeatOnLifecycle(STARTED) { flow.collect { ... } }` — cancels on stop, resumes on start
225
+
226
+ Never use `GlobalScope` or raw `Job()` from within a component with a defined lifecycle.
227
+
228
+ ## Anti-patterns
229
+
230
+ | Anti-pattern | Fix |
231
+ |---|---|
232
+ | `GlobalScope.launch` | Attach to a scope with a known lifecycle |
233
+ | `runBlocking` in request path / library code | Make the function `suspend` |
234
+ | Swallowing `CancellationException` | Rethrow; scope relies on it |
235
+ | `withContext(IO) { ... }` wrapping every line | Switch at I/O boundary only |
236
+ | `async { x() }.await()` with no other work in parallel | Just call `x()` as a suspend fun |
237
+ | `Thread.sleep` in `suspend fun` | Use `delay` |
238
+ | `while (true) { doWork() }` with no yield | Add `yield()` / `ensureActive()` for cancellation |
239
+ | `StateFlow` for events (drops duplicates) | Use `SharedFlow` for events |
240
+ | Long-running Flow in `collect { ... }` on UI thread that blocks | `flowOn(Dispatchers.IO)` upstream |
@@ -0,0 +1,268 @@
1
+ # Kotlin — Idioms
2
+
3
+ Null safety, data classes, sealed classes, scope functions, extension functions.
4
+
5
+ ## `val` / `var` / `const val`
6
+
7
+ ```kotlin
8
+ val name = "A" // immutable reference
9
+ var count = 0 // mutable
10
+ const val MAX = 10 // compile-time constant (top-level or in `object`)
11
+ ```
12
+
13
+ `val` by default. Reach for `var` only when you reassign.
14
+
15
+ ## Nullable types
16
+
17
+ ```kotlin
18
+ var name: String? = null
19
+ name.length // ❌ compile error
20
+ name?.length // Int? — null if name is null
21
+ name!!.length // throws NPE if null — avoid
22
+ name ?: "unknown" // Elvis: use default if null
23
+
24
+ if (name != null) {
25
+ name.length // smart-cast to String
26
+ }
27
+ ```
28
+
29
+ `!!` is a code smell. Every `!!` should have a comment saying why the value is guaranteed non-null there (and, ideally, a test that checks it).
30
+
31
+ ## Type inference
32
+
33
+ ```kotlin
34
+ val n = 10 // Int
35
+ val s = "hi" // String
36
+ val users: List<User> = fetch() // annotate when intent is ambiguous
37
+ ```
38
+
39
+ Annotate public API return types for clarity; let locals infer.
40
+
41
+ ## Data classes
42
+
43
+ ```kotlin
44
+ data class User(val id: String, val email: String, val active: Boolean)
45
+
46
+ val u = User("u1", "a@x.com", true)
47
+ val updated = u.copy(email = "b@x.com")
48
+ u == User("u1", "a@x.com", true) // true — structural equality
49
+ ```
50
+
51
+ Data classes give you `equals`, `hashCode`, `toString`, `copy`, destructuring for free.
52
+
53
+ Don't use data classes for types with behaviour or invariants. If you need a factory or a validation step, make it a regular class with a private constructor + `companion object` factory.
54
+
55
+ ## Sealed classes — the Kotlin discriminated union
56
+
57
+ ```kotlin
58
+ sealed class Result<out T> {
59
+ data class Ok<T>(val value: T) : Result<T>()
60
+ data class Err(val error: String) : Result<Nothing>()
61
+ }
62
+
63
+ fun handle(r: Result<User>) = when (r) {
64
+ is Result.Ok -> use(r.value)
65
+ is Result.Err -> log(r.error)
66
+ // `when` is exhaustive — no else needed
67
+ }
68
+ ```
69
+
70
+ `sealed` limits subclasses to the same file (Kotlin < 1.5) or same module (1.5+).
71
+
72
+ ## `when` — the superior switch
73
+
74
+ ```kotlin
75
+ val label = when (status) {
76
+ Status.Active -> "active"
77
+ Status.Pending -> "pending"
78
+ Status.Banned -> "banned"
79
+ }
80
+
81
+ // Smart patterns
82
+ val x = when {
83
+ n < 0 -> "neg"
84
+ n == 0 -> "zero"
85
+ n in 1..10 -> "small"
86
+ else -> "big"
87
+ }
88
+
89
+ // With is + smart-cast
90
+ when (v) {
91
+ is String -> v.length // v: String
92
+ is Int -> v + 1 // v: Int
93
+ }
94
+ ```
95
+
96
+ Exhaustive `when` over `sealed` / `enum` → no `else` required; the compiler forces you to update every branch when you add a case.
97
+
98
+ ## Scope functions — `let` / `run` / `apply` / `also` / `with`
99
+
100
+ ```kotlin
101
+ user?.let { send(it) } // null-safe "do something with it"
102
+ val formatted = user.run { "$name <$email>" } // use members inside
103
+ val req = Request().apply { url = x; timeout = 5 } // configure, return self
104
+ list.also { log("about to sort: $it") }.sort() // side effect, return self
105
+ ```
106
+
107
+ | | Receiver | Returns |
108
+ |---|---|---|
109
+ | `let` | `it` | lambda result |
110
+ | `run` | `this` | lambda result |
111
+ | `apply` | `this` | receiver |
112
+ | `also` | `it` | receiver |
113
+ | `with(x)` | `this` | lambda result |
114
+
115
+ Quick guide:
116
+ - **Side effect, keep value** → `also`
117
+ - **Configure a builder** → `apply`
118
+ - **Null-safe transformation** → `let`
119
+ - **Computation using members** → `run`
120
+
121
+ Don't chain three+ scope functions — it gets confusing fast.
122
+
123
+ ## Extension functions
124
+
125
+ Add methods to existing types without inheriting.
126
+
127
+ ```kotlin
128
+ fun String.toSlug(): String = lowercase().replace(" ", "-")
129
+
130
+ "Hello World".toSlug() // "hello-world"
131
+ ```
132
+
133
+ Rules:
134
+ - Can't access private members; no reflection shortcuts.
135
+ - Dispatch is static (by declared type, not runtime type) — don't use to "override" methods.
136
+ - Place in a file named for the extended type (`StringExt.kt`) or feature (`Slug.kt`).
137
+
138
+ ## Destructuring
139
+
140
+ ```kotlin
141
+ val (id, email) = user // positional, for data classes
142
+ val (key, value) = map.entries.first()
143
+
144
+ for ((i, x) in list.withIndex()) { ... }
145
+ ```
146
+
147
+ Don't destructure too-far-apart fields; order becomes a secret convention.
148
+
149
+ ## Default & named arguments
150
+
151
+ ```kotlin
152
+ fun send(to: String, subject: String, body: String = "", priority: Int = 0) { ... }
153
+
154
+ send(to = "a@x.com", subject = "hi")
155
+ send("a@x.com", "hi", priority = 1)
156
+ ```
157
+
158
+ Prefer named args at call sites with 3+ params. Defaults obviate overload explosions.
159
+
160
+ ## Single-expression functions
161
+
162
+ ```kotlin
163
+ fun double(x: Int) = x * 2
164
+ fun greet(name: String): String = "hi $name"
165
+ ```
166
+
167
+ Keep for functions that really fit on one line. Force explicit return type on public API.
168
+
169
+ ## Collection API
170
+
171
+ ```kotlin
172
+ // Read-only vs mutable
173
+ val immutable: List<Int> = listOf(1, 2, 3)
174
+ val mutable: MutableList<Int> = mutableListOf(1, 2, 3)
175
+
176
+ // Transformations
177
+ users.filter { it.active }.map { it.email }
178
+ users.groupBy { it.role }
179
+ users.associateBy { it.id } // Map<id, user>
180
+ users.sortedBy { it.name }
181
+ users.partition { it.active } // Pair<active, inactive>
182
+
183
+ // Aggregations
184
+ users.count { it.active }
185
+ users.sumOf { it.balance }
186
+ users.maxByOrNull { it.age }
187
+ ```
188
+
189
+ Chain 2–3 ops. Beyond that, break into named intermediate values or pull into a function.
190
+
191
+ `asSequence()` for lazy evaluation over large collections:
192
+ ```kotlin
193
+ users.asSequence().filter { ... }.map { ... }.take(10).toList()
194
+ ```
195
+
196
+ ## Ranges
197
+
198
+ ```kotlin
199
+ 1..10 // IntRange (inclusive)
200
+ 1 until 10 // IntRange 1..9 (exclusive upper)
201
+ 10 downTo 1 // reversed
202
+ 1..10 step 2 // 1, 3, 5, 7, 9
203
+ ```
204
+
205
+ Prefer ranges over manual `for (i = ...; i < ...; i++)` — clearer intent.
206
+
207
+ ## String templates
208
+
209
+ ```kotlin
210
+ val msg = "Hello $name, you have ${messages.size} messages"
211
+ val raw = """
212
+ Multi-line
213
+ ${expression}
214
+ """.trimIndent()
215
+ ```
216
+
217
+ No need for format strings in most cases.
218
+
219
+ ## Object expressions & declarations
220
+
221
+ ```kotlin
222
+ // Singleton
223
+ object Config {
224
+ val url = "https://api.example.com"
225
+ }
226
+ Config.url
227
+
228
+ // Anonymous object
229
+ val listener = object : Listener {
230
+ override fun onEvent(e: Event) { ... }
231
+ }
232
+ ```
233
+
234
+ Use `object` for singletons and one-off anonymous impls. Avoid `object` for mutable state — it's a global and hard to test.
235
+
236
+ ## `companion object`
237
+
238
+ ```kotlin
239
+ class User private constructor(val id: String) {
240
+ companion object {
241
+ fun create(email: String): User = User(generateId(email))
242
+ }
243
+ }
244
+
245
+ User.create("a@x.com")
246
+ ```
247
+
248
+ Factory methods, constants, static-like helpers live here.
249
+
250
+ ## Java interop essentials
251
+
252
+ - `@JvmStatic` on `companion object` members to expose them as static methods to Java
253
+ - `@JvmField` to expose a `val` / `var` as a Java field
254
+ - `@JvmOverloads` to generate overloads for default params
255
+ - Platform types (`String!`) — values from Java with unknown nullability; treat as non-null by convention OR check.
256
+
257
+ ## Anti-patterns
258
+
259
+ | Anti-pattern | Fix |
260
+ |---|---|
261
+ | `!!` used as "I promise this isn't null" | Restructure so the type proves it; or check and handle |
262
+ | `lateinit` in application code | Use `by lazy { ... }` or pass via constructor |
263
+ | `Unit`-returning function ending with `return Unit` | Implicit; remove |
264
+ | `if-else` chain where `when` is clearer | Use `when` |
265
+ | `var x: List<T> = mutableListOf()` — type vs reality mismatch | Pick one: `val x: MutableList<T>` or use `+` for new lists |
266
+ | Data class used for behaviour-heavy type | Regular class; keep data classes for values |
267
+ | Extension functions that hide bugs (subtle override attempts) | Extension dispatch is static; don't rely on polymorphism |
268
+ | `companion object` holding request-scoped state | Request scope belongs on a request-scoped object |