@booklib/skills 1.2.0 → 1.3.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 (100) hide show
  1. package/CONTRIBUTING.md +122 -0
  2. package/README.md +20 -2
  3. package/ROADMAP.md +36 -0
  4. package/animation-at-work/evals/evals.json +44 -0
  5. package/animation-at-work/examples/after.md +64 -0
  6. package/animation-at-work/examples/before.md +35 -0
  7. package/animation-at-work/scripts/audit_animations.py +295 -0
  8. package/bin/skills.js +552 -42
  9. package/clean-code-reviewer/SKILL.md +109 -1
  10. package/clean-code-reviewer/evals/evals.json +121 -3
  11. package/clean-code-reviewer/examples/after.md +48 -0
  12. package/clean-code-reviewer/examples/before.md +33 -0
  13. package/clean-code-reviewer/references/api_reference.md +158 -0
  14. package/clean-code-reviewer/references/practices-catalog.md +282 -0
  15. package/clean-code-reviewer/references/review-checklist.md +254 -0
  16. package/clean-code-reviewer/scripts/pre-review.py +206 -0
  17. package/data-intensive-patterns/evals/evals.json +43 -0
  18. package/data-intensive-patterns/examples/after.md +61 -0
  19. package/data-intensive-patterns/examples/before.md +38 -0
  20. package/data-intensive-patterns/scripts/adr.py +213 -0
  21. package/data-pipelines/evals/evals.json +45 -0
  22. package/data-pipelines/examples/after.md +97 -0
  23. package/data-pipelines/examples/before.md +37 -0
  24. package/data-pipelines/scripts/new_pipeline.py +444 -0
  25. package/design-patterns/evals/evals.json +46 -0
  26. package/design-patterns/examples/after.md +52 -0
  27. package/design-patterns/examples/before.md +29 -0
  28. package/design-patterns/scripts/scaffold.py +807 -0
  29. package/domain-driven-design/SKILL.md +120 -0
  30. package/domain-driven-design/evals/evals.json +48 -0
  31. package/domain-driven-design/examples/after.md +80 -0
  32. package/domain-driven-design/examples/before.md +43 -0
  33. package/domain-driven-design/scripts/scaffold.py +421 -0
  34. package/effective-java/evals/evals.json +46 -0
  35. package/effective-java/examples/after.md +83 -0
  36. package/effective-java/examples/before.md +37 -0
  37. package/effective-java/scripts/checkstyle_setup.py +211 -0
  38. package/effective-kotlin/evals/evals.json +45 -0
  39. package/effective-kotlin/examples/after.md +36 -0
  40. package/effective-kotlin/examples/before.md +38 -0
  41. package/effective-python/evals/evals.json +44 -0
  42. package/effective-python/examples/after.md +56 -0
  43. package/effective-python/examples/before.md +40 -0
  44. package/effective-python/references/api_reference.md +218 -0
  45. package/effective-python/references/practices-catalog.md +483 -0
  46. package/effective-python/references/review-checklist.md +190 -0
  47. package/effective-python/scripts/lint.py +173 -0
  48. package/kotlin-in-action/evals/evals.json +43 -0
  49. package/kotlin-in-action/examples/after.md +53 -0
  50. package/kotlin-in-action/examples/before.md +39 -0
  51. package/kotlin-in-action/scripts/setup_detekt.py +224 -0
  52. package/lean-startup/evals/evals.json +43 -0
  53. package/lean-startup/examples/after.md +80 -0
  54. package/lean-startup/examples/before.md +34 -0
  55. package/lean-startup/scripts/new_experiment.py +286 -0
  56. package/microservices-patterns/SKILL.md +140 -0
  57. package/microservices-patterns/evals/evals.json +45 -0
  58. package/microservices-patterns/examples/after.md +69 -0
  59. package/microservices-patterns/examples/before.md +40 -0
  60. package/microservices-patterns/scripts/new_service.py +583 -0
  61. package/package.json +2 -8
  62. package/refactoring-ui/evals/evals.json +45 -0
  63. package/refactoring-ui/examples/after.md +85 -0
  64. package/refactoring-ui/examples/before.md +58 -0
  65. package/refactoring-ui/scripts/audit_css.py +250 -0
  66. package/skill-router/SKILL.md +142 -0
  67. package/skill-router/evals/evals.json +38 -0
  68. package/skill-router/examples/after.md +63 -0
  69. package/skill-router/examples/before.md +39 -0
  70. package/skill-router/references/api_reference.md +24 -0
  71. package/skill-router/references/routing-heuristics.md +89 -0
  72. package/skill-router/references/skill-catalog.md +156 -0
  73. package/skill-router/scripts/route.py +266 -0
  74. package/storytelling-with-data/evals/evals.json +47 -0
  75. package/storytelling-with-data/examples/after.md +50 -0
  76. package/storytelling-with-data/examples/before.md +33 -0
  77. package/storytelling-with-data/scripts/chart_review.py +301 -0
  78. package/system-design-interview/evals/evals.json +45 -0
  79. package/system-design-interview/examples/after.md +94 -0
  80. package/system-design-interview/examples/before.md +27 -0
  81. package/system-design-interview/scripts/new_design.py +421 -0
  82. package/using-asyncio-python/evals/evals.json +43 -0
  83. package/using-asyncio-python/examples/after.md +68 -0
  84. package/using-asyncio-python/examples/before.md +39 -0
  85. package/using-asyncio-python/scripts/check_blocking.py +270 -0
  86. package/web-scraping-python/evals/evals.json +46 -0
  87. package/web-scraping-python/examples/after.md +109 -0
  88. package/web-scraping-python/examples/before.md +40 -0
  89. package/web-scraping-python/scripts/new_scraper.py +231 -0
  90. /package/{effective-python-skill → effective-python}/SKILL.md +0 -0
  91. /package/{effective-python-skill → effective-python}/ref-01-pythonic-thinking.md +0 -0
  92. /package/{effective-python-skill → effective-python}/ref-02-lists-and-dicts.md +0 -0
  93. /package/{effective-python-skill → effective-python}/ref-03-functions.md +0 -0
  94. /package/{effective-python-skill → effective-python}/ref-04-comprehensions-generators.md +0 -0
  95. /package/{effective-python-skill → effective-python}/ref-05-classes-interfaces.md +0 -0
  96. /package/{effective-python-skill → effective-python}/ref-06-metaclasses-attributes.md +0 -0
  97. /package/{effective-python-skill → effective-python}/ref-07-concurrency.md +0 -0
  98. /package/{effective-python-skill → effective-python}/ref-08-robustness-performance.md +0 -0
  99. /package/{effective-python-skill → effective-python}/ref-09-testing-debugging.md +0 -0
  100. /package/{effective-python-skill → effective-python}/ref-10-collaboration.md +0 -0
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ lint.py — Ruff linter tuned to Effective Python items.
4
+ Usage: python lint.py <path>
5
+
6
+ Runs ruff with rules that map directly to Effective Python advice,
7
+ then annotates each violation with the relevant item number and title.
8
+ """
9
+
10
+ import json
11
+ import subprocess
12
+ import sys
13
+ import tempfile
14
+ import textwrap
15
+ from pathlib import Path
16
+
17
+ # Maps ruff rule code prefixes/exact codes -> (Item number, short description)
18
+ RULE_TO_ITEM = {
19
+ "E711": ("Item 2", "PEP 8: use 'is None' / 'is not None', not == None"),
20
+ "E712": ("Item 2", "PEP 8: use 'is True' / 'is False', not == True/False"),
21
+ "B006": ("Item 24", "Avoid mutable default arguments"),
22
+ "B007": ("Item 7", "Unused loop variable — use _ for throwaway"),
23
+ "B008": ("Item 24", "Do not call mutable objects as default arguments"),
24
+ "C400": ("Item 27", "Rewrite map() as a list comprehension"),
25
+ "C401": ("Item 27", "Rewrite set() with a comprehension"),
26
+ "C402": ("Item 27", "Rewrite dict() with a dict comprehension"),
27
+ "C403": ("Item 27", "Rewrite list() with a list comprehension"),
28
+ "C404": ("Item 27", "Rewrite list of tuples as dict comprehension"),
29
+ "C405": ("Item 27", "Rewrite set literal — unnecessary literal call"),
30
+ "C406": ("Item 27", "Rewrite dict literal — unnecessary literal call"),
31
+ "C408": ("Item 27", "Rewrite dict()/list()/tuple() with literal"),
32
+ "C409": ("Item 28", "Unnecessary literal in tuple()"),
33
+ "C410": ("Item 28", "Unnecessary literal in list()"),
34
+ "C411": ("Item 27", "Unnecessary list() call"),
35
+ "C413": ("Item 27", "Unnecessary list/reversed() around sorted()"),
36
+ "C414": ("Item 27", "Unnecessary double cast in comprehension"),
37
+ "C415": ("Item 29", "Unnecessary subscript reversal in comprehension"),
38
+ "C416": ("Item 27", "Unnecessary list comprehension — use list()"),
39
+ "C417": ("Item 27", "Unnecessary map() — use generator/comprehension"),
40
+ "SIM101": ("Item 7", "Merge duplicate isinstance() checks with tuple"),
41
+ "SIM102": ("Item 7", "Collapse nested if into single if"),
42
+ "SIM103": ("Item 7", "Return condition directly, not if/else True/False"),
43
+ "SIM105": ("Item 35", "Use contextlib.suppress instead of try/except/pass"),
44
+ "SIM108": ("Item 7", "Use ternary operator instead of if/else block"),
45
+ "SIM110": ("Item 27", "Use comprehension instead of for-loop with append"),
46
+ "SIM115": ("Item 66", "Use context manager for open()"),
47
+ "SIM117": ("Item 66", "Merge nested with statements"),
48
+ "UP001": ("Item 2", "pyupgrade: use modern Python syntax"),
49
+ "UP003": ("Item 2", "pyupgrade: use type() instead of deprecated form"),
50
+ "UP006": ("Item 90", "Use 'list' instead of 'List' for type hints (3.9+)"),
51
+ "UP007": ("Item 90", "Use 'X | Y' instead of 'Optional[X]' (3.10+)"),
52
+ "UP008": ("Item 90", "Use 'super()' without arguments"),
53
+ "UP009": ("Item 2", "pyupgrade: UTF-8 encoding declaration unnecessary"),
54
+ "UP010": ("Item 2", "pyupgrade: unnecessary __future__ import"),
55
+ "UP032": ("Item 4", "Use f-string instead of .format()"),
56
+ "UP034": ("Item 2", "pyupgrade: extraneous parentheses"),
57
+ }
58
+
59
+ RUFF_CONFIG = textwrap.dedent("""\
60
+ [lint]
61
+ select = [
62
+ "E711", "E712",
63
+ "B006", "B007", "B008",
64
+ "C4",
65
+ "SIM",
66
+ "UP",
67
+ ]
68
+ ignore = []
69
+ """)
70
+
71
+
72
+ def find_item(code: str):
73
+ if code in RULE_TO_ITEM:
74
+ return RULE_TO_ITEM[code]
75
+ # Try prefix match (e.g. SIM, UP, C4xx)
76
+ for prefix_len in (5, 4, 3, 2):
77
+ prefix = code[:prefix_len]
78
+ if prefix in RULE_TO_ITEM:
79
+ return RULE_TO_ITEM[prefix]
80
+ return None
81
+
82
+
83
+ def run_ruff(target: Path, config_path: Path):
84
+ cmd = [
85
+ "ruff", "check",
86
+ "--config", str(config_path),
87
+ "--output-format", "json",
88
+ str(target),
89
+ ]
90
+ try:
91
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
92
+ return result.stdout, result.stderr, result.returncode
93
+ except FileNotFoundError:
94
+ return None, "NOT_FOUND", 1
95
+ except subprocess.TimeoutExpired:
96
+ return None, "TIMEOUT", 1
97
+
98
+
99
+ def main():
100
+ if len(sys.argv) < 2:
101
+ print("Usage: python lint.py <path>")
102
+ sys.exit(1)
103
+
104
+ target = Path(sys.argv[1])
105
+ if not target.exists():
106
+ print(f"Error: path not found: {target}")
107
+ sys.exit(1)
108
+
109
+ with tempfile.NamedTemporaryFile(
110
+ mode="w", suffix=".toml", prefix="ruff_ep_", delete=False
111
+ ) as tmp:
112
+ tmp.write(RUFF_CONFIG)
113
+ config_path = Path(tmp.name)
114
+
115
+ try:
116
+ stdout, stderr, returncode = run_ruff(target, config_path)
117
+ finally:
118
+ config_path.unlink(missing_ok=True)
119
+
120
+ if stdout is None:
121
+ if stderr == "NOT_FOUND":
122
+ print("Error: ruff is not installed.")
123
+ print("Install it with: pip install ruff")
124
+ print("Or globally: pipx install ruff")
125
+ elif stderr == "TIMEOUT":
126
+ print("Error: ruff timed out.")
127
+ else:
128
+ print(f"Error running ruff: {stderr}")
129
+ sys.exit(1)
130
+
131
+ print(f"Effective Python lint report — {target}")
132
+ print("-" * 70)
133
+
134
+ try:
135
+ violations = json.loads(stdout) if stdout.strip() else []
136
+ except json.JSONDecodeError:
137
+ print("Raw ruff output:")
138
+ print(stdout)
139
+ sys.exit(returncode)
140
+
141
+ if not violations:
142
+ print("No violations found. Code aligns well with Effective Python.")
143
+ sys.exit(0)
144
+
145
+ # Group by item
146
+ by_item: dict[str, list] = {}
147
+ for v in violations:
148
+ code = v.get("code", "?")
149
+ mapping = find_item(code)
150
+ item_key = mapping[0] if mapping else "Other"
151
+ by_item.setdefault(item_key, []).append((v, mapping))
152
+
153
+ total = 0
154
+ for item_key in sorted(by_item):
155
+ entries = by_item[item_key]
156
+ item_desc = entries[0][1][1] if entries[0][1] else "ruff violation"
157
+ print(f"\n[{item_key}] {item_desc}")
158
+ for v, _ in entries:
159
+ loc = v.get("location", {})
160
+ row, col = loc.get("row", "?"), loc.get("column", "?")
161
+ filename = Path(v.get("filename", str(target))).name
162
+ message = v.get("message", "")
163
+ code = v.get("code", "?")
164
+ print(f" {filename}:{row}:{col} [{code}] {message}")
165
+ total += 1
166
+
167
+ print(f"\n{'-' * 70}")
168
+ print(f"Total violations: {total}")
169
+ sys.exit(1 if violations else 0)
170
+
171
+
172
+ if __name__ == "__main__":
173
+ main()
@@ -0,0 +1,43 @@
1
+ {
2
+ "evals": [
3
+ {
4
+ "id": "eval-01-java-style-null-handling",
5
+ "prompt": "Review this Kotlin code:\n\n```kotlin\nclass OrderService(private val repo: OrderRepository) {\n\n fun getShippingCity(orderId: String): String {\n val order = repo.findById(orderId)\n if (order != null) {\n val address = order.shippingAddress\n if (address != null) {\n val city = address.city\n if (city != null) {\n return city.uppercase()\n }\n }\n }\n return \"UNKNOWN\"\n }\n\n fun applyDiscount(orderId: String, percent: Double): Double {\n val order = repo.findById(orderId)\n var total = 0.0\n if (order != null) {\n total = order.total\n val membership = order.customer.membership\n if (membership != null) {\n if (membership.isActive != null && membership.isActive == true) {\n total = total * (1.0 - percent / 100.0)\n }\n }\n }\n return total\n }\n}\n```",
6
+ "expectations": [
7
+ "Flags the deeply nested null-checking pyramid in `getShippingCity` as Java-style; recommends replacing with safe call chaining: `repo.findById(orderId)?.shippingAddress?.city?.uppercase() ?: \"UNKNOWN\"` (Ch 7: safe calls and Elvis operator)",
8
+ "Flags the nested null checks in `applyDiscount` as Java-style; recommends safe calls and let for null-safe transformations (Ch 7)",
9
+ "Flags `membership.isActive == true` as redundant when `isActive` is nullable Boolean; recommends `membership.isActive == true` can be replaced with `membership.isActive ?: false` or smart cast after null check (Ch 7)",
10
+ "Notes that `var total = 0.0` followed by conditional assignment is a code smell; recommends using an expression: `val total = order?.total ?: 0.0` (Ch 2: val over var, expression style)",
11
+ "Recommends using `let` for null-safe scoping: `membership?.takeIf { it.isActive == true }?.let { ... }` (Ch 7: let for null checks)",
12
+ "Provides a refactored version using safe calls, Elvis operator, and let throughout"
13
+ ]
14
+ },
15
+ {
16
+ "id": "eval-02-missing-extension-functions-named-args",
17
+ "prompt": "Review this Kotlin code:\n\n```kotlin\nfun formatUserDisplay(firstName: String, lastName: String, email: String, isAdmin: Boolean): String {\n val name = firstName + \" \" + lastName\n val badge = if (isAdmin == true) \"[ADMIN] \" else \"\"\n return badge + name + \" <\" + email + \">\"\n}\n\nfun truncate(text: String, maxLen: Int, suffix: String): String {\n return if (text.length > maxLen) {\n text.substring(0, maxLen) + suffix\n } else {\n text\n }\n}\n\nclass UserDisplayHelper {\n fun render(firstName: String, lastName: String, email: String, isAdmin: Boolean): String {\n return formatUserDisplay(firstName, lastName, email, isAdmin)\n }\n\n fun renderTruncated(firstName: String, lastName: String, email: String, isAdmin: Boolean, maxLen: Int): String {\n val display = formatUserDisplay(firstName, lastName, email, isAdmin)\n return truncate(display, maxLen, \"...\")\n }\n}\n```",
18
+ "expectations": [
19
+ "Flags string concatenation with `+` throughout; recommends string templates (Ch 2: string templates are idiomatic Kotlin)",
20
+ "Flags `isAdmin == true` comparison on a non-nullable Boolean; should be just `isAdmin` (Ch 7: unnecessary null comparison on non-nullable type)",
21
+ "Notes that `truncate` and `formatUserDisplay` are standalone utility functions that would be more idiomatic as extension functions on `String` (Ch 3: prefer extension functions over utility classes)",
22
+ "Flags `UserDisplayHelper` as an unnecessary Java-style utility class wrapping top-level functions; recommends converting to top-level functions or extension functions (Ch 3: top-level functions replace static utility classes)",
23
+ "Identifies that `renderTruncated` has many same-typed parameters in a row which are ambiguous at call sites; recommends named arguments or introducing a value class or data class (Ch 3: use named/default arguments for clarity)",
24
+ "Notes that `truncate` could use a default parameter value for `suffix` instead of requiring callers to pass `\"...\"` every time (Ch 3: default parameter values)",
25
+ "Provides a refactored version using extension functions on String, string templates, and named/default args"
26
+ ]
27
+ },
28
+ {
29
+ "id": "eval-03-clean-kotlin-coroutines-sealed",
30
+ "prompt": "Review this Kotlin code:\n\n```kotlin\nsealed interface PaymentResult {\n data class Success(val transactionId: String, val amount: Double) : PaymentResult\n data class Declined(val reason: String, val code: Int) : PaymentResult\n data class NetworkError(val cause: Throwable) : PaymentResult\n}\n\ninterface PaymentGateway {\n suspend fun charge(amount: Double, token: String): PaymentResult\n}\n\nclass PaymentProcessor(\n private val gateway: PaymentGateway,\n private val scope: CoroutineScope\n) {\n suspend fun processOrder(orderId: String, amount: Double, token: String): PaymentResult {\n return withContext(Dispatchers.IO) {\n try {\n gateway.charge(amount, token)\n } catch (e: CancellationException) {\n throw e\n } catch (e: Exception) {\n PaymentResult.NetworkError(e)\n }\n }\n }\n\n fun processOrderAsync(orderId: String, amount: Double, token: String) =\n scope.async { processOrder(orderId, amount, token) }\n}\n\nfun renderResult(result: PaymentResult): String = when (result) {\n is PaymentResult.Success -> \"Charged \\$${result.amount} — txn ${result.transactionId}\"\n is PaymentResult.Declined -> \"Declined (${result.code}): ${result.reason}\"\n is PaymentResult.NetworkError -> \"Network error: ${result.cause.message}\"\n}\n```",
31
+ "expectations": [
32
+ "Recognizes this is already idiomatic, well-structured Kotlin and says so explicitly",
33
+ "Praises the sealed interface hierarchy for `PaymentResult` providing exhaustive when expressions without a catch-all branch (Ch 4: sealed classes for restricted hierarchies)",
34
+ "Praises re-throwing `CancellationException` to maintain cooperative coroutine cancellation (Ch 14: structured concurrency, cancellation handling)",
35
+ "Praises using `withContext(Dispatchers.IO)` for the blocking gateway call rather than blocking on a non-IO dispatcher (Ch 14: dispatcher usage)",
36
+ "Praises use of `data class` subtypes giving automatic `equals`, `hashCode`, `copy`, and `toString` (Ch 4: data classes for value types)",
37
+ "Praises the exhaustive `when` expression in `renderResult` that the compiler enforces due to the sealed hierarchy (Ch 4)",
38
+ "Does NOT manufacture issues to appear thorough; any suggestions are explicitly framed as minor optional improvements",
39
+ "May note minor optional suggestions such as whether `processOrderAsync` is needed given `processOrder` is already suspend, but frames them as design questions, not violations"
40
+ ]
41
+ }
42
+ ]
43
+ }
@@ -0,0 +1,53 @@
1
+ # After
2
+
3
+ Idiomatic Kotlin using a sealed interface for channels, safe-call operators, named parameters, and extension functions — eliminating all manual null checks and string comparisons.
4
+
5
+ ```kotlin
6
+ // Sealed interface models exactly the valid channels — exhaustive when is enforced
7
+ sealed interface NotificationChannel {
8
+ data class Email(val address: String) : NotificationChannel
9
+ data class Sms(val phoneNumber: String) : NotificationChannel
10
+ }
11
+
12
+ // Extension function resolves the preferred channel from a User
13
+ fun User.preferredChannel(): NotificationChannel? = when {
14
+ email != null -> NotificationChannel.Email(email)
15
+ phoneNumber != null -> NotificationChannel.Sms(phoneNumber)
16
+ else -> null
17
+ }
18
+
19
+ class NotificationService(
20
+ private val emailSender: EmailSender,
21
+ private val smsSender: SmsSender,
22
+ ) {
23
+
24
+ fun sendNotification(user: User, message: String, channel: NotificationChannel) {
25
+ require(message.isNotBlank()) { "Notification message must not be blank" }
26
+
27
+ when (channel) {
28
+ is NotificationChannel.Email -> {
29
+ val subject = "Notification for ${user.firstName} ${user.lastName}"
30
+ emailSender.send(to = channel.address, subject = subject, body = message)
31
+ }
32
+ is NotificationChannel.Sms -> {
33
+ smsSender.send(to = channel.phoneNumber, body = message)
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ // Usage — caller resolves channel; service focuses on delivery
40
+ fun notifyUser(user: User, message: String, service: NotificationService) {
41
+ user.preferredChannel()
42
+ ?.let { channel -> service.sendNotification(user, message, channel) }
43
+ ?: logger.warn("No notification channel available for user ${user.id}")
44
+ }
45
+ ```
46
+
47
+ Key improvements:
48
+ - `sealed interface NotificationChannel` replaces the `String` channel parameter — the compiler enforces exhaustive `when` and eliminates the "Unknown channel" else branch (Ch 4: Sealed classes)
49
+ - `User?` parameter becomes non-null `User` — the caller is responsible for ensuring a valid user exists; null-safety is pushed to the boundary (Ch 7: Null safety)
50
+ - `user.preferredChannel()` extension function encapsulates the channel-resolution logic outside the service class (Ch 3: Extension functions)
51
+ - `require(message.isNotBlank())` replaces silent println for invalid input (Effective Kotlin Item 5: Specify your expectations on arguments)
52
+ - Named parameters `to =`, `subject =`, `body =` make the send calls self-documenting (Ch 3: Named arguments)
53
+ - String template `"${user.firstName} ${user.lastName}"` replaces concatenation (Ch 2: String templates)
@@ -0,0 +1,39 @@
1
+ # Before
2
+
3
+ Kotlin code written with Java-style null checks, no extension functions, and no use of sealed classes or safe-call operators.
4
+
5
+ ```kotlin
6
+ class NotificationService(
7
+ private val emailSender: EmailSender,
8
+ private val smsSender: SmsSender
9
+ ) {
10
+
11
+ fun sendNotification(user: User?, message: String?, channel: String) {
12
+ if (user == null) {
13
+ println("User is null")
14
+ return
15
+ }
16
+ if (message == null || message.isEmpty()) {
17
+ println("Message is empty")
18
+ return
19
+ }
20
+
21
+ if (channel == "EMAIL") {
22
+ if (user.email != null) {
23
+ val subject = "Notification for " + user.firstName + " " + user.lastName
24
+ emailSender.send(user.email, subject, message)
25
+ } else {
26
+ println("No email for user " + user.id)
27
+ }
28
+ } else if (channel == "SMS") {
29
+ if (user.phoneNumber != null) {
30
+ smsSender.send(user.phoneNumber, message)
31
+ } else {
32
+ println("No phone for user " + user.id)
33
+ }
34
+ } else {
35
+ println("Unknown channel: " + channel)
36
+ }
37
+ }
38
+ }
39
+ ```
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ setup_detekt.py - Set up Detekt with Kotlin-in-Action aligned rules.
4
+
5
+ Usage:
6
+ python setup_detekt.py [--output-dir ./]
7
+
8
+ Generates:
9
+ detekt.yml - Detekt config with rules mapped to Kotlin in Action chapters
10
+ run_detekt.sh - Shell script to run Detekt with this config
11
+
12
+ Each rule includes a comment referencing the relevant chapter from
13
+ "Kotlin in Action" by Dmitry Jemerov and Svetlana Isakova.
14
+ """
15
+
16
+ import argparse
17
+ import pathlib
18
+ import stat
19
+
20
+ DETEKT_YML = """\
21
+ # detekt.yml
22
+ # Detekt configuration aligned with "Kotlin in Action" by Jemerov & Isakova.
23
+ # Each rule references the chapter that motivates it.
24
+
25
+ build:
26
+ maxIssues: 0
27
+ excludeCorrectable: false
28
+
29
+ config:
30
+ validation: true
31
+ warningsAsErrors: false
32
+
33
+ complexity:
34
+ active: true
35
+
36
+ # Chapter 5 - Lambdas: keep lambdas and functions concise and readable.
37
+ LongMethod:
38
+ active: true
39
+ threshold: 20
40
+
41
+ # Chapter 3 - Functions: prefer functions with few, well-named parameters.
42
+ # Many parameters suggest the need for a data class or builder pattern.
43
+ LongParameterList:
44
+ active: true
45
+ functionThreshold: 4
46
+ constructorThreshold: 6
47
+ ignoreDefaultParameters: true
48
+
49
+ # Chapter 10 - Higher-order functions: avoid deep nesting; flatten with
50
+ # higher-order functions (map, filter, let, run) instead.
51
+ NestedBlockDepth:
52
+ active: true
53
+ threshold: 3
54
+
55
+ # Chapter 4 - Classes: classes should be focused and not overly complex.
56
+ TooManyFunctions:
57
+ active: true
58
+ thresholdInFiles: 20
59
+ thresholdInClasses: 15
60
+ thresholdInInterfaces: 10
61
+ thresholdInObjects: 10
62
+ thresholdInEnums: 5
63
+
64
+ naming:
65
+ active: true
66
+
67
+ # Chapter 3 - Functions: Kotlin convention is camelCase for function names.
68
+ FunctionNaming:
69
+ active: true
70
+ functionPattern: '[a-z][a-zA-Z0-9]*'
71
+ excludes: ['**/test/**']
72
+
73
+ # Chapter 2 - Basics: variables follow camelCase; properties are idiomatic Kotlin.
74
+ VariableNaming:
75
+ active: true
76
+ variablePattern: '[a-z][a-zA-Z0-9]*'
77
+ privateVariablePattern: '(_)?[a-z][a-zA-Z0-9]*'
78
+
79
+ # Chapter 4 - Classes: class names are PascalCase per Kotlin convention.
80
+ ClassNaming:
81
+ active: true
82
+ classPattern: '[A-Z][a-zA-Z0-9]*'
83
+
84
+ # Chapter 8 - Generics: type parameter names should be single uppercase letters
85
+ # or descriptive PascalCase names.
86
+ TypeParameterNaming:
87
+ active: true
88
+ typeParameterPattern: '[A-Z][A-Za-z]*'
89
+
90
+ style:
91
+ active: true
92
+
93
+ # Chapter 4 - Classes, Objects, Interfaces: abstract classes without abstract
94
+ # members should be interfaces or open classes instead.
95
+ UnnecessaryAbstractClass:
96
+ active: true
97
+
98
+ # Chapter 3 - Functions: Unit return type is implicit; declaring it is redundant.
99
+ OptionalUnit:
100
+ active: true
101
+
102
+ # Chapter 2 - Basics: val properties that can be const should be const
103
+ # for compile-time optimisation.
104
+ MayBeConst:
105
+ active: true
106
+
107
+ # Chapter 4 - Objects: use object declarations instead of classes with only
108
+ # static members.
109
+ UseDataClass:
110
+ active: true
111
+ allowVars: false
112
+
113
+ # Chapter 11 - DSL: trailing lambdas should be outside parentheses per convention.
114
+ UnnecessaryParentheses:
115
+ active: true
116
+
117
+ potential-bugs:
118
+ active: true
119
+
120
+ # Chapter 6 - Null Safety: lateinit signals deferred initialisation, which
121
+ # makes nullability guarantees harder to reason about. Prefer constructor injection.
122
+ LateinitUsage:
123
+ active: true
124
+ excludes: ['**/test/**']
125
+ ignoreOnClassesPattern: ''
126
+
127
+ # Chapter 6 - Null Safety: the !! operator bypasses null safety and will
128
+ # throw NPE at runtime. Use safe calls (?.) or Elvis operator (?:) instead.
129
+ UnsafeCallOnNullableType:
130
+ active: true
131
+ excludes: ['**/test/**']
132
+
133
+ # Chapter 6 - Null Safety: explicit null checks with == null should be
134
+ # replaced with safe-call or let idioms.
135
+ NullableToStringCall:
136
+ active: true
137
+
138
+ coroutines:
139
+ active: true
140
+
141
+ # Chapter 12 (Appendix) - Coroutines: GlobalScope couples coroutines to
142
+ # application lifetime and makes cancellation impossible. Use structured
143
+ # concurrency with a scoped CoroutineScope instead.
144
+ GlobalCoroutineUsage:
145
+ active: true
146
+
147
+ # Chapter 12 - Coroutines: suspend modifier on a function that contains no
148
+ # suspension points is misleading and adds unnecessary overhead.
149
+ RedundantSuspendModifier:
150
+ active: true
151
+
152
+ # Chapter 12 - Coroutines: use withContext for blocking calls rather than
153
+ # running them on the default dispatcher.
154
+ BlockingMethodInNonBlockingContext:
155
+ active: true
156
+ """
157
+
158
+ RUN_SCRIPT = """\
159
+ #!/usr/bin/env bash
160
+ # run_detekt.sh
161
+ # Runs Detekt with the Kotlin-in-Action aligned configuration.
162
+ # Generated by setup_detekt.py
163
+
164
+ set -euo pipefail
165
+
166
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
167
+ CONFIG="${SCRIPT_DIR}/detekt.yml"
168
+ SRC_DIR="${1:-src}"
169
+
170
+ if ! command -v detekt &>/dev/null && ! command -v detekt-cli &>/dev/null; then
171
+ echo "ERROR: detekt not found on PATH."
172
+ echo ""
173
+ echo "Install options:"
174
+ echo " brew install detekt # macOS"
175
+ echo " sdk install detekt # SDKMAN"
176
+ echo " # Or run via Gradle plugin - add to build.gradle.kts:"
177
+ echo " # plugins { id(\\"io.gitlab.arturbosch.detekt\\") version \\"1.23.5\\" }"
178
+ exit 1
179
+ fi
180
+
181
+ CMD="detekt"
182
+ command -v detekt-cli &>/dev/null && CMD="detekt-cli"
183
+
184
+ echo "Running Detekt on $SRC_DIR ..."
185
+ $CMD --config "$CONFIG" --input "$SRC_DIR"
186
+ echo "Done."
187
+ """
188
+
189
+
190
+ def write_file(path: pathlib.Path, content: str, executable: bool = False) -> None:
191
+ path.write_text(content, encoding="utf-8")
192
+ if executable:
193
+ path.chmod(path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
194
+ print(f"Wrote: {path}")
195
+
196
+
197
+ def main() -> None:
198
+ parser = argparse.ArgumentParser(
199
+ description="Set up Detekt with Kotlin-in-Action aligned rules."
200
+ )
201
+ parser.add_argument(
202
+ "--output-dir",
203
+ default=".",
204
+ help="Directory to write config files (default: ./)",
205
+ )
206
+ args = parser.parse_args()
207
+
208
+ output_dir = pathlib.Path(args.output_dir).resolve()
209
+ output_dir.mkdir(parents=True, exist_ok=True)
210
+
211
+ write_file(output_dir / "detekt.yml", DETEKT_YML)
212
+ write_file(output_dir / "run_detekt.sh", RUN_SCRIPT, executable=True)
213
+
214
+ print()
215
+ print("Setup complete.")
216
+ print(f" Config : {output_dir / 'detekt.yml'}")
217
+ print(f" Runner : {output_dir / 'run_detekt.sh'}")
218
+ print()
219
+ print("Run Detekt:")
220
+ print(f" cd {output_dir} && ./run_detekt.sh [src-dir]")
221
+
222
+
223
+ if __name__ == "__main__":
224
+ main()
@@ -0,0 +1,43 @@
1
+ {
2
+ "evals": [
3
+ {
4
+ "id": "eval-01-six-month-build-before-user-testing",
5
+ "prompt": "Review this product development plan:\n\n```\nProduct: TaskFlow — AI-powered project management for remote teams\nTeam: 4 engineers, 1 designer, 1 product manager\nTimeline: 6-month build before any user testing\n\nMonth 1-2: Core task management (create, assign, due dates, status)\nMonth 3: AI features (auto-prioritization, workload balancing)\nMonth 4: Integrations (Slack, GitHub, Jira, Google Calendar)\nMonth 5: Mobile apps (iOS and Android)\nMonth 6: Analytics dashboard and reporting\n\nSuccess criteria: Ship to beta users after 6 months\nMarketing plan: Product Hunt launch in month 7\n\nAssumptions we're making:\n- Remote teams struggle with task prioritization\n- AI suggestions will be trusted and acted on\n- Teams are willing to switch from their current PM tool\n- Integration with existing tools is a must-have on day one\n```",
6
+ "expectations": [
7
+ "Identifies the 6-month build before any user testing as a direct violation of the Build-Measure-Learn loop: validated learning cannot happen without users (Ch 3-4: validated learning, experimentation over planning)",
8
+ "Flags the listed assumptions ('remote teams struggle', 'AI suggestions will be trusted', 'willing to switch') as leap-of-faith assumptions that should be tested first, not assumed (Ch 5: identify and test leap-of-faith assumptions before building)",
9
+ "Flags the absence of any hypothesis or success metric beyond 'ship to beta': recommends defining a value hypothesis and growth hypothesis with measurable success criteria (Ch 5: value hypothesis and growth hypothesis)",
10
+ "Flags the absence of an MVP: 6 months of features is not an MVP — it is a v1.0 product launch; recommends identifying the single riskiest assumption and testing it with the minimum viable experiment (Ch 6: MVP is for learning, not launching)",
11
+ "Flags that integrations, mobile apps, and analytics are included before validating whether users even want the core task management and AI features; recommends cutting scope to the riskiest assumption (Ch 6: smallest experiment to test the riskiest assumption)",
12
+ "Flags the absence of innovation accounting: there are no cohort metrics, funnel metrics, or baseline measurements planned (Ch 7: innovation accounting)",
13
+ "Recommends concrete alternatives such as a concierge MVP (manually prioritize tasks for 5 teams for 2 weeks) or a Wizard of Oz test (fake AI powered by a human) to validate the AI trust assumption before building it"
14
+ ]
15
+ },
16
+ {
17
+ "id": "eval-02-feature-request-without-validated-learning",
18
+ "prompt": "Review this feature request and justification:\n\n```\nFeature Request: Advanced Filtering and Saved Views\n\nSubmitted by: Product Manager\nPriority: High\n\nRequest:\nAdd advanced filtering to the task list — filter by assignee, due date range,\ntag, project, and custom fields. Users should be able to save filter combinations\nas named views (e.g., \"My overdue tasks\", \"Sprint backlog\").\n\nJustification:\n- 3 customers mentioned filtering in support tickets last quarter\n- Our top competitor, Asana, has this feature\n- The engineering team estimates 3 sprints (6 weeks) to build\n- A customer on a discovery call said \"filtering would be great\"\n\nExpected outcome:\nImprove user retention and increase upsell conversion to Pro tier.\n\nApproval requested: Start sprint planning next week\n```",
19
+ "expectations": [
20
+ "Flags that 'customers mentioned in support tickets' and 'a customer said it would be great' are anecdotal, not validated learning — these are opinions, not measured behavior (Ch 3: validated learning requires empirical evidence, not opinions)",
21
+ "Flags competitor benchmarking ('Asana has this feature') as a reason to build without testing whether this feature drives retention or conversion for this specific user base (Ch 5: test your own value hypothesis, not competitors')",
22
+ "Flags that the expected outcome 'improve retention and increase upsell' is stated without a hypothesis, baseline metric, or success threshold; recommends defining a testable hypothesis before committing 6 weeks of engineering (Ch 7: innovation accounting — establish baseline, define success threshold)",
23
+ "Flags no mention of which leap-of-faith assumption is being tested: is the assumption that users can't find what they need, that they churn because of missing filters, or that Pro upsell is blocked by this feature? (Ch 5: identify the specific assumption)",
24
+ "Recommends a smaller experiment to validate before building: add a prominent 'Filter tasks' button that shows a 'Coming soon — join waitlist' modal and measure click rate to quantify demand (Ch 6: smallest experiment to validate the assumption)",
25
+ "Flags the absence of a pivot/persevere decision framework: what happens if the feature ships and retention does not improve? (Ch 8: define pivot/persevere criteria before building)"
26
+ ]
27
+ },
28
+ {
29
+ "id": "eval-03-well-structured-mvp-experiment",
30
+ "prompt": "Review this product experiment plan:\n\n```\nExperiment: Validate demand for AI-assisted code review summaries\n\nHypothesis:\nWe believe that engineering managers at companies with 10-50 engineers\nspend more than 2 hours per week reading through pull request diffs to\nstay informed. We believe they will pay for an automated daily digest\nthat summarizes what changed, why, and any risks flagged.\n\nRiskiest assumption: Managers will read and act on AI-generated summaries\nrather than skimming or ignoring them.\n\nMVP Design (Wizard of Oz — 3-week test):\n- Recruit 8 engineering managers via LinkedIn outreach\n- Each connects their GitHub repo (OAuth)\n- Every weekday morning, a human analyst (us) reads the day's PRs\n and writes a 200-word summary email manually\n- We send it from a branded email: digest@codebrief.io\n- Participants do not know summaries are human-written\n\nSuccess metrics (measured after 3 weeks):\n- Primary: Open rate >= 70% across all 5 weekday sends\n- Secondary: At least 5 of 8 managers respond to exit survey saying\n they found summaries 'useful' or 'very useful'\n- Conversion signal: At least 3 managers ask 'how do I keep this?'\n\nPivot/persevere criteria:\n- If primary AND secondary metrics are met: build v1 with real AI\n- If only one is met: run a second 2-week test with adjusted format\n- If neither is met: pivot to a different user segment or problem\n\nTimeline: Recruiting this week, experiment runs weeks 2-4, decision week 5\n```",
31
+ "expectations": [
32
+ "Recognizes this is a well-structured Lean Startup experiment and says so explicitly",
33
+ "Praises the falsifiable hypothesis that names a specific user segment, problem, and measurable behavior (Ch 3: validated learning with empirical evidence)",
34
+ "Praises identifying the riskiest assumption ('managers will act on summaries') separately from the general hypothesis — this is the correct application of leap-of-faith assumption identification (Ch 5: leap-of-faith assumptions)",
35
+ "Praises the Wizard of Oz MVP design that tests the value hypothesis without building any AI infrastructure (Ch 6: MVP is for learning, not launching — concierge/Wizard of Oz types)",
36
+ "Praises the quantified success metrics with specific thresholds (70% open rate, 5/8 satisfaction, 3 conversion signals) rather than vague goals (Ch 7: actionable metrics, not vanity metrics)",
37
+ "Praises the explicit pivot/persevere criteria defined before the experiment runs, making the decision data-driven rather than gut-driven (Ch 8: structured pivot/persevere decision)",
38
+ "Praises the tight 5-week timeline keeping the Build-Measure-Learn loop short (Ch 9: small batches, fast learning cycles)",
39
+ "Does NOT manufacture issues to appear thorough; any suggestions are explicitly framed as minor optional improvements"
40
+ ]
41
+ }
42
+ ]
43
+ }