cc-codeconductor 0.4.2 → 0.5.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 (172) hide show
  1. package/README.md +75 -4
  2. package/dist/index.js +2698 -230
  3. package/package.json +1 -1
  4. package/presets/agy/skills/evaluation/SKILL.md +6 -0
  5. package/presets/agy/skills/openspec/SKILL.md +32 -0
  6. package/presets/agy/workflows/cc-openspec.md +62 -0
  7. package/presets/agy/workflows/cc-pipeline.md +69 -0
  8. package/presets/agy/workflows/cc-scorecard.md +17 -0
  9. package/presets/claude/commands/cc/openspec.md +144 -0
  10. package/presets/claude/commands/cc/review.md +13 -2
  11. package/presets/claude/commands/cc/scorecard.md +65 -0
  12. package/presets/claude/skills/evaluation/SKILL.md +42 -0
  13. package/presets/claude/skills/openspec/SKILL.md +54 -0
  14. package/presets/codex/AGENTS.md +57 -0
  15. package/presets/cursor/.cursorignore +15 -0
  16. package/presets/cursor/AGENTS.md +504 -0
  17. package/presets/cursor/agents/architect.md +211 -0
  18. package/presets/cursor/agents/complexity-auditor.md +76 -0
  19. package/presets/cursor/agents/contract-builder.md +75 -0
  20. package/presets/cursor/agents/docs.md +180 -0
  21. package/presets/cursor/agents/goal-planner.md +71 -0
  22. package/presets/cursor/agents/implementer.md +161 -0
  23. package/presets/cursor/agents/orchestrator.md +377 -0
  24. package/presets/cursor/agents/repo-explorer.md +100 -0
  25. package/presets/cursor/agents/reviewer.md +237 -0
  26. package/presets/cursor/agents/security-reviewer.md +113 -0
  27. package/presets/cursor/agents/task-coach.md +145 -0
  28. package/presets/cursor/agents/tester.md +241 -0
  29. package/presets/cursor/commands/cc/api-contract.md +58 -0
  30. package/presets/cursor/commands/cc/db-migration.md +58 -0
  31. package/presets/cursor/commands/cc/feature.md +115 -0
  32. package/presets/cursor/commands/cc/fix.md +121 -0
  33. package/presets/cursor/commands/cc/openspec.md +144 -0
  34. package/presets/cursor/commands/cc/pagespeed.md +103 -0
  35. package/presets/cursor/commands/cc/refactor.md +148 -0
  36. package/presets/cursor/commands/cc/review.md +137 -0
  37. package/presets/cursor/commands/cc/scorecard.md +65 -0
  38. package/presets/cursor/commands/cc/tdd-cycle.md +226 -0
  39. package/presets/cursor/commands/cc/test-plan.md +138 -0
  40. package/presets/cursor/rules/behavioral-discipline.mdc +14 -0
  41. package/presets/cursor/rules/context-budget.mdc +12 -0
  42. package/presets/cursor/rules/orchestration.mdc +12 -0
  43. package/presets/cursor/rules/yagni-stdlib.mdc +11 -0
  44. package/presets/cursor/skills/android/SKILL.md +122 -0
  45. package/presets/cursor/skills/api-versioning/SKILL.md +394 -0
  46. package/presets/cursor/skills/astro/SKILL.md +322 -0
  47. package/presets/cursor/skills/auth-token-inspector/SKILL.md +33 -0
  48. package/presets/cursor/skills/code-review/SKILL.md +208 -0
  49. package/presets/cursor/skills/conductor-setup/SKILL.md +127 -0
  50. package/presets/cursor/skills/django-orm/SKILL.md +463 -0
  51. package/presets/cursor/skills/django-testing/SKILL.md +417 -0
  52. package/presets/cursor/skills/django-uv/SKILL.md +409 -0
  53. package/presets/cursor/skills/drizzle-schema-architect/SKILL.md +54 -0
  54. package/presets/cursor/skills/evaluation/SKILL.md +8 -0
  55. package/presets/cursor/skills/fastapi-pydantic-strict/SKILL.md +46 -0
  56. package/presets/cursor/skills/find-skills/SKILL.md +144 -0
  57. package/presets/cursor/skills/jpa-nplusone-detector/SKILL.md +49 -0
  58. package/presets/cursor/skills/jpa-postgres/SKILL.md +626 -0
  59. package/presets/cursor/skills/laravel-specialist/SKILL.md +267 -0
  60. package/presets/cursor/skills/laravel-specialist/references/eloquent.md +351 -0
  61. package/presets/cursor/skills/laravel-specialist/references/livewire.md +512 -0
  62. package/presets/cursor/skills/laravel-specialist/references/queues.md +423 -0
  63. package/presets/cursor/skills/laravel-specialist/references/routing.md +362 -0
  64. package/presets/cursor/skills/laravel-specialist/references/testing.md +522 -0
  65. package/presets/cursor/skills/livewire-alpine-bridge/SKILL.md +39 -0
  66. package/presets/cursor/skills/multi-agent-orchestration/README.md +144 -0
  67. package/presets/cursor/skills/multi-agent-orchestration/SKILL.md +579 -0
  68. package/presets/cursor/skills/multi-agent-orchestration/examples/framework_implementations.py +362 -0
  69. package/presets/cursor/skills/multi-agent-orchestration/examples/orchestration_patterns.py +411 -0
  70. package/presets/cursor/skills/multi-agent-orchestration/scripts/agent_communication.py +334 -0
  71. package/presets/cursor/skills/multi-agent-orchestration/scripts/benchmarking.py +341 -0
  72. package/presets/cursor/skills/multi-agent-orchestration/scripts/workflow_management.py +334 -0
  73. package/presets/cursor/skills/nextjs-typescript/SKILL.md +394 -0
  74. package/presets/cursor/skills/openspec/SKILL.md +52 -0
  75. package/presets/cursor/skills/pagespeed-insights/SKILL.md +445 -0
  76. package/presets/cursor/skills/pagespeed-insights/reference.md +50 -0
  77. package/presets/cursor/skills/pagespeed-perf/SKILL.md +281 -0
  78. package/presets/cursor/skills/php-pro/SKILL.md +210 -0
  79. package/presets/cursor/skills/php-pro/references/async-patterns.md +412 -0
  80. package/presets/cursor/skills/php-pro/references/laravel-patterns.md +377 -0
  81. package/presets/cursor/skills/php-pro/references/modern-php-features.md +323 -0
  82. package/presets/cursor/skills/php-pro/references/symfony-patterns.md +466 -0
  83. package/presets/cursor/skills/php-pro/references/testing-quality.md +466 -0
  84. package/presets/cursor/skills/python/SKILL.md +613 -0
  85. package/presets/cursor/skills/python-django-stack/SKILL.md +500 -0
  86. package/presets/cursor/skills/python-fastapi-stack/SKILL.md +464 -0
  87. package/presets/cursor/skills/security/SKILL.md +384 -0
  88. package/presets/cursor/skills/seo-analytics-injector/SKILL.md +44 -0
  89. package/presets/cursor/skills/spring-auth-auditor/SKILL.md +33 -0
  90. package/presets/cursor/skills/spring-boot-feature/SKILL.md +566 -0
  91. package/presets/cursor/skills/spring-boot-kotlin/SKILL.md +408 -0
  92. package/presets/cursor/skills/spring-boot-testing-strategy/SKILL.md +479 -0
  93. package/presets/cursor/skills/sqlalchemy/SKILL.md +473 -0
  94. package/presets/cursor/skills/tailwind-responsive-auditor/SKILL.md +32 -0
  95. package/presets/cursor/skills/tdd-mutation-tester/SKILL.md +28 -0
  96. package/presets/cursor/skills/testing-tdd/SKILL.md +592 -0
  97. package/presets/cursor/skills/workflow-orchestration-patterns/SKILL.md +98 -0
  98. package/presets/cursor/skills/workflow-orchestration-patterns/references/details.md +223 -0
  99. package/presets/laravel-tall/agents/architect.md +8 -0
  100. package/presets/laravel-tall/agents/implementer.md +12 -0
  101. package/presets/laravel-tall/laravel-tall.yml +38 -0
  102. package/presets/opencode/agents/architect.md +153 -61
  103. package/presets/opencode/agents/complexity-auditor.md +1 -0
  104. package/presets/opencode/agents/contract-builder.md +93 -0
  105. package/presets/opencode/agents/docs.md +125 -40
  106. package/presets/opencode/agents/goal-planner.md +82 -0
  107. package/presets/opencode/agents/implementer.md +107 -38
  108. package/presets/opencode/agents/orchestrator.md +90 -71
  109. package/presets/opencode/agents/repo-explorer.md +1 -2
  110. package/presets/opencode/agents/reviewer.md +164 -75
  111. package/presets/opencode/agents/security-reviewer.md +129 -0
  112. package/presets/opencode/agents/task-coach.md +110 -59
  113. package/presets/opencode/agents/tester.md +1 -2
  114. package/presets/opencode/commands/cc-openspec.md +61 -0
  115. package/presets/opencode/commands/cc-scorecard.md +16 -0
  116. package/presets/opencode/prompts/v0.4.0/architect.md +221 -0
  117. package/presets/opencode/prompts/v0.4.0/complexity-auditor.md +89 -0
  118. package/presets/opencode/prompts/v0.4.0/docs.md +189 -0
  119. package/presets/opencode/prompts/v0.4.0/implementer.md +162 -0
  120. package/presets/opencode/prompts/v0.4.0/orchestrator.md +348 -0
  121. package/presets/opencode/prompts/v0.4.0/repo-explorer.md +110 -0
  122. package/presets/opencode/prompts/v0.4.0/reviewer.md +225 -0
  123. package/presets/opencode/prompts/v0.4.0/task-coach.md +155 -0
  124. package/presets/opencode/prompts/v0.4.0/tester.md +251 -0
  125. package/presets/opencode/prompts/v0.5.0/architect.md +222 -0
  126. package/presets/opencode/prompts/v0.5.0/complexity-auditor.md +91 -0
  127. package/presets/opencode/prompts/v0.5.0/contract-builder.md +84 -0
  128. package/presets/opencode/prompts/v0.5.0/docs.md +190 -0
  129. package/presets/opencode/prompts/v0.5.0/goal-planner.md +80 -0
  130. package/presets/opencode/prompts/v0.5.0/implementer.md +171 -0
  131. package/presets/opencode/prompts/v0.5.0/orchestrator.md +388 -0
  132. package/presets/opencode/prompts/v0.5.0/repo-explorer.md +111 -0
  133. package/presets/opencode/prompts/v0.5.0/reviewer.md +248 -0
  134. package/presets/opencode/prompts/v0.5.0/security-reviewer.md +123 -0
  135. package/presets/opencode/prompts/v0.5.0/task-coach.md +156 -0
  136. package/presets/opencode/prompts/v0.5.0/tester.md +252 -0
  137. package/presets/opencode/skills/auth-token-inspector/SKILL.md +31 -0
  138. package/presets/opencode/skills/drizzle-schema-architect/SKILL.md +51 -0
  139. package/presets/opencode/skills/evaluation/SKILL.md +6 -0
  140. package/presets/opencode/skills/fastapi-pydantic-strict/SKILL.md +44 -0
  141. package/presets/opencode/skills/jpa-nplusone-detector/SKILL.md +46 -0
  142. package/presets/opencode/skills/livewire-alpine-bridge/SKILL.md +36 -0
  143. package/presets/opencode/skills/openspec/SKILL.md +50 -0
  144. package/presets/opencode/skills/seo-analytics-injector/SKILL.md +44 -0
  145. package/presets/opencode/skills/spring-auth-auditor/SKILL.md +30 -0
  146. package/presets/opencode/skills/tailwind-responsive-auditor/SKILL.md +30 -0
  147. package/presets/opencode/skills/tdd-mutation-tester/SKILL.md +28 -0
  148. package/presets/python-data-api/agents/architect.md +8 -0
  149. package/presets/python-data-api/agents/implementer.md +9 -0
  150. package/presets/python-data-api/python-data-api.yml +37 -0
  151. package/presets/spring-kotlin-jpa/agents/architect.md +8 -0
  152. package/presets/spring-kotlin-jpa/agents/implementer.md +9 -0
  153. package/presets/spring-kotlin-jpa/spring-kotlin-jpa.yml +38 -0
  154. package/presets/templates/BACKLOG.md +33 -0
  155. package/presets/templates/execution-profile.yml +6 -0
  156. package/presets/templates/model-comparison.md +11 -0
  157. package/presets/templates/regression-checklist.yml +10 -0
  158. package/presets/ts-next-drizzle/agents/architect.md +8 -0
  159. package/presets/ts-next-drizzle/agents/implementer.md +10 -0
  160. package/presets/ts-next-drizzle/ts-next-drizzle.yml +41 -0
  161. package/src/presets/manifests/agy.yml +2 -2
  162. package/src/presets/manifests/claude.yml +2 -2
  163. package/src/presets/manifests/codex.yml +2 -2
  164. package/src/presets/manifests/cursor.yml +19 -3
  165. package/src/presets/manifests/gemini.yml +2 -2
  166. package/src/presets/manifests/opencode.yml +2 -2
  167. package/src/presets/models/agy.yml +21 -0
  168. package/src/presets/models/claude.yml +18 -0
  169. package/src/presets/models/codex.yml +18 -0
  170. package/src/presets/models/cursor.yml +39 -9
  171. package/src/presets/models/gemini.yml +18 -0
  172. package/src/presets/models/opencode.yml +18 -0
@@ -0,0 +1,566 @@
1
+ ---
2
+ id: spring-boot-feature
3
+ version: 1.0.0
4
+ name: Spring Boot Feature Creation
5
+ description: >
6
+ Guides the creation of complete Spring Boot features following a structured,
7
+ layer-by-layer workflow: entity, repository, service, controller, and tests.
8
+ Applies Kotlin idioms, Bean Validation, and MockK test patterns throughout.
9
+
10
+ user-invokable: true
11
+ license: MIT
12
+ metadata:
13
+ author: lgzarturo
14
+ category: spring
15
+
16
+ compatibility:
17
+ tools: [claude, codex, gemini, agy, opencode]
18
+ stacks:
19
+ languages: [kotlin]
20
+ frameworks: [spring-boot, spring-mvc, spring-data-jpa, mockk, junit5]
21
+ databases: [postgresql, h2]
22
+
23
+ risk:
24
+ level: medium
25
+ can_execute_shell: false
26
+ can_modify_files: true
27
+ requires_network: false
28
+
29
+ inputs:
30
+ - feature description
31
+ - existing domain context (if any)
32
+ - build.gradle.kts
33
+ - application.yml
34
+
35
+ outputs:
36
+ - domain entity
37
+ - JPA repository interface
38
+ - service class with business logic
39
+ - REST controller with validations
40
+ - request/response DTOs
41
+ - unit tests (MockK)
42
+ - controller integration tests (MockMvc)
43
+
44
+ quality:
45
+ reviewed_by: codeconductor-core
46
+ version: 0.1.0
47
+ paths:
48
+ - "**/*.kt"
49
+ - "**/build.gradle.kts"
50
+ ---
51
+ # Spring Boot Feature Creation
52
+
53
+ When asked to create a feature, follow these steps **in order**. Do not skip
54
+ steps or combine layers. Each layer has a single responsibility.
55
+
56
+ **When to ask questions:** Only ask when there is genuine ambiguity in business
57
+ logic — for example, what happens when a duplicate is found, or whether soft
58
+ delete is required. Do not ask about technical choices (naming, package
59
+ structure, framework configuration) — apply the conventions in this skill.
60
+
61
+ ---
62
+
63
+ ## Step 1 — Entity + Repository
64
+
65
+ Create the JPA entity and its repository before any other layer.
66
+
67
+ ### Entity
68
+
69
+ ```kotlin
70
+ @Entity
71
+ @Table(
72
+ name = "orders",
73
+ indexes = [Index(columnList = "customer_id")]
74
+ )
75
+ class Order(
76
+ @Id
77
+ @GeneratedValue(strategy = GenerationType.UUID)
78
+ val id: UUID = UUID.randomUUID(),
79
+
80
+ @Column(name = "customer_id", nullable = false)
81
+ val customerId: UUID,
82
+
83
+ @Column(name = "status", nullable = false)
84
+ @Enumerated(EnumType.STRING)
85
+ var status: OrderStatus = OrderStatus.PENDING,
86
+
87
+ @Column(name = "total_amount", nullable = false)
88
+ var totalAmount: BigDecimal,
89
+
90
+ @Column(name = "created_at", nullable = false, updatable = false)
91
+ @CreatedDate
92
+ val createdAt: Instant = Instant.now(),
93
+
94
+ @Column(name = "updated_at", nullable = false)
95
+ @LastModifiedDate
96
+ var updatedAt: Instant = Instant.now()
97
+ )
98
+
99
+ enum class OrderStatus { PENDING, CONFIRMED, CANCELLED }
100
+ ```
101
+
102
+ Rules:
103
+
104
+ - `@Table` with explicit `name` — never rely on inferred table names
105
+ - `@Column(nullable = false)` always explicit for non-nullable fields
106
+ - UUID primary keys with `GenerationType.UUID`
107
+ - Enums stored as `STRING`, not `ORDINAL`
108
+ - Include `createdAt` and `updatedAt` on every entity
109
+ - Never use `data class` for JPA entities — Hibernate requires a no-arg
110
+ constructor and mutable state; use `class`
111
+ - `equals`/`hashCode` based on `id` only, or omit (reference equality is safe
112
+ for JPA entities when managed by the same `EntityManager`)
113
+
114
+ ### Repository
115
+
116
+ ```kotlin
117
+ @Repository
118
+ interface OrderRepository : JpaRepository<Order, UUID> {
119
+ fun findByCustomerId(customerId: UUID): List<Order>
120
+ fun findByCustomerIdAndStatus(customerId: UUID, status: OrderStatus): List<Order>
121
+ fun existsByCustomerIdAndStatus(customerId: UUID, status: OrderStatus): Boolean
122
+
123
+ @Query("SELECT o FROM Order o WHERE o.status = :status AND o.createdAt < :before")
124
+ fun findExpiredPending(
125
+ @Param("status") status: OrderStatus,
126
+ @Param("before") before: Instant
127
+ ): List<Order>
128
+ }
129
+ ```
130
+
131
+ Rules:
132
+
133
+ - Extend `JpaRepository<Entity, IdType>`
134
+ - Use derived query method names for simple conditions
135
+ - Use `@Query` with JPQL for joins, multi-condition queries, or aggregations
136
+ - Use native SQL only when JPQL cannot express it — add `nativeQuery = true`
137
+ - Never add `@Transactional` to repository methods — Spring Data handles it
138
+
139
+ ---
140
+
141
+ ## Step 2 — Service
142
+
143
+ The service is the single owner of all business logic. No logic in controllers,
144
+ no logic in repositories.
145
+
146
+ ```kotlin
147
+ @Service
148
+ @Transactional
149
+ class OrderService(
150
+ private val orderRepository: OrderRepository,
151
+ private val customerRepository: CustomerRepository
152
+ ) {
153
+
154
+ @Transactional(readOnly = true)
155
+ fun getById(id: UUID): Order =
156
+ orderRepository.findById(id).orElseThrow { OrderNotFoundException(id) }
157
+
158
+ @Transactional(readOnly = true)
159
+ fun listByCustomer(customerId: UUID): List<Order> =
160
+ orderRepository.findByCustomerId(customerId)
161
+
162
+ fun create(customerId: UUID, request: CreateOrderRequest): Order {
163
+ if (!customerRepository.existsById(customerId)) {
164
+ throw CustomerNotFoundException(customerId)
165
+ }
166
+ val order = Order(
167
+ customerId = customerId,
168
+ totalAmount = request.totalAmount
169
+ )
170
+ return orderRepository.save(order)
171
+ }
172
+
173
+ fun cancel(id: UUID): Order {
174
+ val order = getById(id)
175
+ if (order.status == OrderStatus.CANCELLED) {
176
+ throw OrderAlreadyCancelledException(id)
177
+ }
178
+ order.status = OrderStatus.CANCELLED
179
+ return orderRepository.save(order)
180
+ }
181
+ }
182
+ ```
183
+
184
+ Rules:
185
+
186
+ - `@Transactional` at class level — applies to all public methods
187
+ - `readOnly = true` on read-only methods — prevents dirty checking, faster
188
+ - Never put `@Transactional` on private methods — Spring proxies cannot
189
+ intercept them
190
+ - Throw typed domain exceptions — never `RuntimeException` or `Exception`
191
+ directly
192
+ - Never inject repositories into controllers — only services cross that boundary
193
+
194
+ ### Domain Exceptions
195
+
196
+ Define typed exceptions per feature:
197
+
198
+ ```kotlin
199
+ class OrderNotFoundException(id: UUID) :
200
+ RuntimeException("Order not found: $id")
201
+
202
+ class OrderAlreadyCancelledException(id: UUID) :
203
+ RuntimeException("Order already cancelled: $id")
204
+
205
+ class CustomerNotFoundException(id: UUID) :
206
+ RuntimeException("Customer not found: $id")
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Step 3 — Controller + DTOs
212
+
213
+ The controller translates HTTP into service calls. No business logic here.
214
+
215
+ ### DTOs
216
+
217
+ Define request and response data classes with Bean Validation annotations:
218
+
219
+ ```kotlin
220
+ data class CreateOrderRequest(
221
+ @field:NotNull(message = "totalAmount is required")
222
+ @field:DecimalMin(value = "0.01", message = "totalAmount must be greater than zero")
223
+ val totalAmount: BigDecimal
224
+ )
225
+
226
+ data class OrderResponse(
227
+ val id: UUID,
228
+ val customerId: UUID,
229
+ val status: String,
230
+ val totalAmount: BigDecimal,
231
+ val createdAt: Instant
232
+ )
233
+
234
+ fun Order.toResponse(): OrderResponse = OrderResponse(
235
+ id = id,
236
+ customerId = customerId,
237
+ status = status.name,
238
+ totalAmount = totalAmount,
239
+ createdAt = createdAt
240
+ )
241
+ ```
242
+
243
+ Rules for DTOs:
244
+
245
+ - Use `data class` — immutable, no `@Entity`
246
+ - Use `@field:` prefix on validation annotations — Kotlin applies annotations to
247
+ the property by default, not the backing field; Spring reads the field
248
+ - Define a `toResponse()` extension function on the entity — do not expose
249
+ entities directly in responses
250
+ - Never return `@Entity` from a controller method
251
+
252
+ ### Controller
253
+
254
+ ```kotlin
255
+ @RestController
256
+ @RequestMapping("/api/v1/customers/{customerId}/orders")
257
+ class OrderController(private val orderService: OrderService) {
258
+
259
+ @GetMapping
260
+ fun list(@PathVariable customerId: UUID): ResponseEntity<List<OrderResponse>> =
261
+ ResponseEntity.ok(orderService.listByCustomer(customerId).map { it.toResponse() })
262
+
263
+ @GetMapping("/{id}")
264
+ fun get(
265
+ @PathVariable customerId: UUID,
266
+ @PathVariable id: UUID
267
+ ): ResponseEntity<OrderResponse> =
268
+ ResponseEntity.ok(orderService.getById(id).toResponse())
269
+
270
+ @PostMapping
271
+ fun create(
272
+ @PathVariable customerId: UUID,
273
+ @Valid @RequestBody request: CreateOrderRequest
274
+ ): ResponseEntity<OrderResponse> {
275
+ val order = orderService.create(customerId, request)
276
+ return ResponseEntity.status(HttpStatus.CREATED).body(order.toResponse())
277
+ }
278
+
279
+ @DeleteMapping("/{id}/cancel")
280
+ fun cancel(
281
+ @PathVariable customerId: UUID,
282
+ @PathVariable id: UUID
283
+ ): ResponseEntity<OrderResponse> =
284
+ ResponseEntity.ok(orderService.cancel(id).toResponse())
285
+ }
286
+ ```
287
+
288
+ Rules:
289
+
290
+ - One controller per feature
291
+ - `@Valid` on every `@RequestBody` parameter
292
+ - Return `ResponseEntity<T>` — explicit HTTP status control
293
+ - No try/catch in controllers — use `GlobalExceptionHandler`
294
+ - Map entities to DTOs before returning — never return the entity itself
295
+
296
+ ### Global Exception Handler
297
+
298
+ Add new exception mappings to the existing `GlobalExceptionHandler`. Do not
299
+ create a second one.
300
+
301
+ ```kotlin
302
+ @ControllerAdvice
303
+ class GlobalExceptionHandler {
304
+
305
+ @ExceptionHandler(OrderNotFoundException::class)
306
+ fun handleOrderNotFound(ex: OrderNotFoundException): ResponseEntity<ErrorResponse> =
307
+ ResponseEntity.status(HttpStatus.NOT_FOUND)
308
+ .body(ErrorResponse(error = ex.message ?: "Order not found", code = "ORDER_NOT_FOUND"))
309
+
310
+ @ExceptionHandler(OrderAlreadyCancelledException::class)
311
+ fun handleAlreadyCancelled(ex: OrderAlreadyCancelledException): ResponseEntity<ErrorResponse> =
312
+ ResponseEntity.status(HttpStatus.CONFLICT)
313
+ .body(ErrorResponse(error = ex.message ?: "Order already cancelled", code = "ORDER_ALREADY_CANCELLED"))
314
+
315
+ @ExceptionHandler(MethodArgumentNotValidException::class)
316
+ fun handleValidation(ex: MethodArgumentNotValidException): ResponseEntity<ErrorResponse> {
317
+ val message = ex.bindingResult.fieldErrors
318
+ .joinToString(", ") { "${it.field}: ${it.defaultMessage}" }
319
+ return ResponseEntity.badRequest()
320
+ .body(ErrorResponse(error = message, code = "VALIDATION_ERROR"))
321
+ }
322
+ }
323
+
324
+ data class ErrorResponse(val error: String, val code: String)
325
+ ```
326
+
327
+ ---
328
+
329
+ ## Step 4 — Tests with MockK
330
+
331
+ Write three test classes per feature: unit tests for the service, controller
332
+ tests with MockMvc, and repository tests with `@DataJpaTest`.
333
+
334
+ ### Service Unit Tests (MockK)
335
+
336
+ ```kotlin
337
+ @ExtendWith(MockKExtension::class)
338
+ class OrderServiceTest {
339
+
340
+ @MockK
341
+ private lateinit var orderRepository: OrderRepository
342
+
343
+ @MockK
344
+ private lateinit var customerRepository: CustomerRepository
345
+
346
+ private lateinit var orderService: OrderService
347
+
348
+ @BeforeEach
349
+ fun setUp() {
350
+ orderService = OrderService(orderRepository, customerRepository)
351
+ }
352
+
353
+ @Nested
354
+ inner class Create {
355
+
356
+ @Test
357
+ fun `should create order when customer exists`() {
358
+ // Arrange
359
+ val customerId = UUID.randomUUID()
360
+ val request = CreateOrderRequest(totalAmount = BigDecimal("99.99"))
361
+ val savedOrder = Order(customerId = customerId, totalAmount = request.totalAmount)
362
+ every { customerRepository.existsById(customerId) } returns true
363
+ every { orderRepository.save(any()) } returns savedOrder
364
+
365
+ // Act
366
+ val result = orderService.create(customerId, request)
367
+
368
+ // Assert
369
+ assertThat(result.customerId).isEqualTo(customerId)
370
+ assertThat(result.totalAmount).isEqualByComparingTo(BigDecimal("99.99"))
371
+ verify(exactly = 1) { orderRepository.save(any()) }
372
+ }
373
+
374
+ @Test
375
+ fun `should throw CustomerNotFoundException when customer does not exist`() {
376
+ // Arrange
377
+ val customerId = UUID.randomUUID()
378
+ every { customerRepository.existsById(customerId) } returns false
379
+
380
+ // Act + Assert
381
+ assertThrows<CustomerNotFoundException> {
382
+ orderService.create(customerId, CreateOrderRequest(totalAmount = BigDecimal("10.00")))
383
+ }
384
+ verify(exactly = 0) { orderRepository.save(any()) }
385
+ }
386
+ }
387
+
388
+ @Nested
389
+ inner class Cancel {
390
+
391
+ @Test
392
+ fun `should cancel order when status is PENDING`() {
393
+ // Arrange
394
+ val order = Order(customerId = UUID.randomUUID(), totalAmount = BigDecimal("50.00"))
395
+ every { orderRepository.findById(order.id) } returns Optional.of(order)
396
+ every { orderRepository.save(any()) } answers { firstArg() }
397
+
398
+ // Act
399
+ val result = orderService.cancel(order.id)
400
+
401
+ // Assert
402
+ assertThat(result.status).isEqualTo(OrderStatus.CANCELLED)
403
+ }
404
+
405
+ @Test
406
+ fun `should throw OrderAlreadyCancelledException when order is already cancelled`() {
407
+ // Arrange
408
+ val order = Order(
409
+ customerId = UUID.randomUUID(),
410
+ totalAmount = BigDecimal("50.00"),
411
+ status = OrderStatus.CANCELLED
412
+ )
413
+ every { orderRepository.findById(order.id) } returns Optional.of(order)
414
+
415
+ // Act + Assert
416
+ assertThrows<OrderAlreadyCancelledException> {
417
+ orderService.cancel(order.id)
418
+ }
419
+ }
420
+ }
421
+ }
422
+ ```
423
+
424
+ ### Controller Tests (MockMvc)
425
+
426
+ ```kotlin
427
+ @SpringBootTest
428
+ @AutoConfigureMockMvc
429
+ class OrderControllerTest {
430
+
431
+ @Autowired
432
+ private lateinit var mockMvc: MockMvc
433
+
434
+ @Autowired
435
+ private lateinit var objectMapper: ObjectMapper
436
+
437
+ @MockkBean
438
+ private lateinit var orderService: OrderService
439
+
440
+ @Test
441
+ fun `should return 201 and created order when request is valid`() {
442
+ // Arrange
443
+ val customerId = UUID.randomUUID()
444
+ val order = Order(customerId = customerId, totalAmount = BigDecimal("99.99"))
445
+ every { orderService.create(customerId, any()) } returns order
446
+
447
+ val body = mapOf("totalAmount" to "99.99")
448
+
449
+ // Act + Assert
450
+ mockMvc.perform(
451
+ post("/api/v1/customers/$customerId/orders")
452
+ .contentType(MediaType.APPLICATION_JSON)
453
+ .content(objectMapper.writeValueAsString(body))
454
+ )
455
+ .andExpect(status().isCreated)
456
+ .andExpect(jsonPath("$.customerId").value(customerId.toString()))
457
+ .andExpect(jsonPath("$.status").value("PENDING"))
458
+ }
459
+
460
+ @Test
461
+ fun `should return 400 when totalAmount is missing`() {
462
+ mockMvc.perform(
463
+ post("/api/v1/customers/${UUID.randomUUID()}/orders")
464
+ .contentType(MediaType.APPLICATION_JSON)
465
+ .content("{}")
466
+ )
467
+ .andExpect(status().isBadRequest)
468
+ .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"))
469
+ }
470
+
471
+ @Test
472
+ fun `should return 404 when order is not found`() {
473
+ val customerId = UUID.randomUUID()
474
+ val orderId = UUID.randomUUID()
475
+ every { orderService.getById(orderId) } throws OrderNotFoundException(orderId)
476
+
477
+ mockMvc.perform(get("/api/v1/customers/$customerId/orders/$orderId"))
478
+ .andExpect(status().isNotFound)
479
+ .andExpect(jsonPath("$.code").value("ORDER_NOT_FOUND"))
480
+ }
481
+ }
482
+ ```
483
+
484
+ ### Repository Tests
485
+
486
+ ```kotlin
487
+ @DataJpaTest
488
+ class OrderRepositoryTest {
489
+
490
+ @Autowired
491
+ private lateinit var orderRepository: OrderRepository
492
+
493
+ @Test
494
+ fun `should find orders by customer id`() {
495
+ // Arrange
496
+ val customerId = UUID.randomUUID()
497
+ orderRepository.save(Order(customerId = customerId, totalAmount = BigDecimal("10.00")))
498
+ orderRepository.save(Order(customerId = UUID.randomUUID(), totalAmount = BigDecimal("20.00")))
499
+
500
+ // Act
501
+ val result = orderRepository.findByCustomerId(customerId)
502
+
503
+ // Assert
504
+ assertThat(result).hasSize(1)
505
+ assertThat(result.first().customerId).isEqualTo(customerId)
506
+ }
507
+ }
508
+ ```
509
+
510
+ MockK rules:
511
+
512
+ - Use `@ExtendWith(MockKExtension::class)` — not `@MockBean` for unit tests
513
+ - Use `@MockkBean` (MockK Spring integration) only in `@SpringBootTest` context
514
+ - `every { }` for stubbing, `verify { }` for verification
515
+ - `answers { firstArg() }` to return the argument passed to `save()`
516
+ - `verify(exactly = 0) { }` to assert a method was never called
517
+ - Never use `relaxed = true` unless you genuinely do not care about any return
518
+ value
519
+ - Test names: `should [expected outcome] when [condition]`
520
+ - Test structure: Arrange / Act / Assert, separated by blank lines
521
+
522
+ ---
523
+
524
+ ## Step 5 — When to Ask
525
+
526
+ Only pause to ask when there is genuine ambiguity in business logic. Do not ask
527
+ about technical choices — apply the conventions in this skill.
528
+
529
+ **Ask when:**
530
+
531
+ - The feature description does not specify what happens on conflict (duplicate,
532
+ constraint violation, concurrent modification)
533
+ - It is unclear whether delete means hard delete or soft delete
534
+ - Authorization rules are not specified and cannot be inferred from context
535
+ - A domain invariant is referenced but not defined (e.g., "validate the order"
536
+ without specifying which fields or rules)
537
+
538
+ **Do not ask about:**
539
+
540
+ - Package structure — always feature-oriented MVC
541
+ - Naming conventions — follow the existing codebase
542
+ - Whether to use MockK or Mockito — always MockK
543
+ - Whether to add `@Transactional` — always at the service class level
544
+ - Whether to validate at controller or service — always at DTO layer
545
+
546
+ ---
547
+
548
+ ## Package Structure
549
+
550
+ Always feature-oriented. One package per feature, not one package per layer.
551
+
552
+ ```
553
+ src/main/kotlin/{base-package}/{feature}/
554
+ controller/ # HTTP only — OrderController, DTOs, toResponse() extensions
555
+ service/ # Business logic — OrderService, domain exceptions
556
+ repository/ # Data access — OrderRepository
557
+ domain/ # JPA entities — Order, OrderStatus
558
+
559
+ src/test/kotlin/{base-package}/{feature}/
560
+ controller/ # OrderControllerTest (MockMvc)
561
+ service/ # OrderServiceTest (MockK)
562
+ repository/ # OrderRepositoryTest (@DataJpaTest)
563
+ ```
564
+
565
+ Never create `src/main/kotlin/controllers/`, `src/main/kotlin/services/`, etc.
566
+ Layer-first structure does not scale and breaks feature cohesion.