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,626 @@
1
+ ---
2
+ id: jpa-postgres
3
+ version: 1.0.0
4
+ name: JPA + PostgreSQL
5
+ description: >
6
+ Provides expert knowledge of JPA entity design, relationship mapping, Flyway
7
+ migrations, and PostgreSQL-specific optimizations.
8
+
9
+ user-invokable: true
10
+ license: MIT
11
+ metadata:
12
+ author: lgzarturo
13
+ category: database
14
+
15
+ compatibility:
16
+ tools: [claude, codex, gemini, agy, opencode]
17
+ stacks:
18
+ languages: [kotlin, java]
19
+ frameworks: [spring-boot, spring-data-jpa, hibernate]
20
+ databases: [postgresql]
21
+
22
+ risk:
23
+ level: high
24
+ can_execute_shell: false
25
+ can_modify_files: true
26
+ requires_network: false
27
+
28
+ inputs:
29
+ - source_files
30
+ - migration scripts
31
+ - entity classes
32
+ - repository interfaces
33
+
34
+ outputs:
35
+ - JPA entity classes
36
+ - repository interfaces
37
+ - Flyway migration scripts
38
+ - JPQL and native queries
39
+ - integration test classes
40
+
41
+ quality:
42
+ reviewed_by: codeconductor-core
43
+ version: 0.1.0
44
+ paths:
45
+ - "**/*.kt"
46
+ - "**/*.java"
47
+ ---
48
+ # JPA + PostgreSQL
49
+
50
+ ## Entity Design
51
+
52
+ ### Primary Keys
53
+
54
+ Use UUIDs. Do not use auto-increment integers as public-facing identifiers.
55
+
56
+ ```kotlin
57
+ @Entity
58
+ @Table(name = "users")
59
+ class User(
60
+ @Id
61
+ @GeneratedValue(strategy = GenerationType.UUID)
62
+ val id: UUID = UUID.randomUUID(),
63
+
64
+ @Column(name = "email", nullable = false, unique = true, length = 255)
65
+ var email: String,
66
+
67
+ @Column(name = "name", nullable = false, length = 100)
68
+ var name: String
69
+ )
70
+ ```
71
+
72
+ UUID generation strategy `GenerationType.UUID` is available in Hibernate 6+
73
+ (Spring Boot 3+). For earlier versions, use `@UuidGenerator` from Hibernate or
74
+ generate manually.
75
+
76
+ ### Auditing
77
+
78
+ Enable automatic timestamp management with Spring Data auditing.
79
+
80
+ ```kotlin
81
+ // Enable in main application class or a @Configuration class
82
+ @EnableJpaAuditing
83
+ @SpringBootApplication
84
+ class Application
85
+
86
+ // Base class for auditable entities
87
+ @MappedSuperclass
88
+ @EntityListeners(AuditingEntityListener::class)
89
+ abstract class AuditableEntity {
90
+
91
+ @Column(name = "created_at", nullable = false, updatable = false)
92
+ @CreatedDate
93
+ lateinit var createdAt: Instant
94
+
95
+ @Column(name = "updated_at", nullable = false)
96
+ @LastModifiedDate
97
+ lateinit var updatedAt: Instant
98
+ }
99
+
100
+ // Entity extends the base class
101
+ @Entity
102
+ @Table(name = "users")
103
+ class User(
104
+ @Id @GeneratedValue(strategy = GenerationType.UUID)
105
+ val id: UUID = UUID.randomUUID(),
106
+
107
+ @Column(name = "email", nullable = false, unique = true)
108
+ var email: String,
109
+
110
+ var name: String
111
+ ) : AuditableEntity()
112
+ ```
113
+
114
+ ### Soft Delete
115
+
116
+ Do not delete rows — mark them as deleted and filter them transparently.
117
+
118
+ ```kotlin
119
+ @Entity
120
+ @Table(name = "users")
121
+ @SQLDelete(sql = "UPDATE users SET deleted_at = NOW() WHERE id = ?")
122
+ @FilterDef(name = "deletedFilter", parameters = [ParamDef(name = "isDeleted", type = Boolean::class)])
123
+ @Filter(name = "deletedFilter", condition = "deleted_at IS NULL")
124
+ class User(
125
+ @Id @GeneratedValue(strategy = GenerationType.UUID)
126
+ val id: UUID = UUID.randomUUID(),
127
+
128
+ @Column(name = "email", nullable = false, unique = true)
129
+ var email: String,
130
+
131
+ @Column(name = "deleted_at")
132
+ var deletedAt: Instant? = null
133
+ )
134
+ ```
135
+
136
+ Alternative using `@Where` (simpler, but applies globally without ability to
137
+ disable):
138
+
139
+ ```kotlin
140
+ @Entity
141
+ @Table(name = "users")
142
+ @SQLDelete(sql = "UPDATE users SET deleted_at = NOW() WHERE id = ?")
143
+ @Where(clause = "deleted_at IS NULL")
144
+ class User(...)
145
+ ```
146
+
147
+ ### Column Annotations
148
+
149
+ Always be explicit. Never rely on Hibernate defaults.
150
+
151
+ ```kotlin
152
+ @Column(
153
+ name = "email", // explicit column name
154
+ nullable = false, // maps to NOT NULL constraint
155
+ unique = true, // maps to UNIQUE constraint
156
+ length = 255 // VARCHAR(255) — ignored for TEXT type
157
+ )
158
+ var email: String
159
+ ```
160
+
161
+ For PostgreSQL `TEXT` type (unbounded), use `columnDefinition`:
162
+
163
+ ```kotlin
164
+ @Column(name = "description", nullable = false, columnDefinition = "TEXT")
165
+ var description: String
166
+ ```
167
+
168
+ ### Indexes
169
+
170
+ Define indexes on the entity, not in migration scripts, for searchable fields.
171
+
172
+ ```kotlin
173
+ @Entity
174
+ @Table(
175
+ name = "users",
176
+ indexes = [
177
+ Index(name = "idx_users_email", columnList = "email", unique = true),
178
+ Index(name = "idx_users_created_at", columnList = "created_at")
179
+ ]
180
+ )
181
+ class User(...)
182
+ ```
183
+
184
+ For partial indexes (e.g., soft-delete), use a migration script — JPA cannot
185
+ express partial indexes.
186
+
187
+ ## Relationship Mapping
188
+
189
+ ### One-to-Many
190
+
191
+ ```kotlin
192
+ @Entity
193
+ @Table(name = "orders")
194
+ class Order(
195
+ @Id @GeneratedValue(strategy = GenerationType.UUID)
196
+ val id: UUID = UUID.randomUUID(),
197
+
198
+ @OneToMany(
199
+ mappedBy = "order",
200
+ cascade = [CascadeType.ALL],
201
+ fetch = FetchType.LAZY, // ALWAYS lazy by default
202
+ orphanRemoval = true
203
+ )
204
+ val items: MutableList<OrderItem> = mutableListOf()
205
+ )
206
+
207
+ @Entity
208
+ @Table(name = "order_items")
209
+ class OrderItem(
210
+ @Id @GeneratedValue(strategy = GenerationType.UUID)
211
+ val id: UUID = UUID.randomUUID(),
212
+
213
+ @ManyToOne(fetch = FetchType.LAZY)
214
+ @JoinColumn(name = "order_id", nullable = false)
215
+ val order: Order,
216
+
217
+ @Column(name = "quantity", nullable = false)
218
+ var quantity: Int
219
+ )
220
+ ```
221
+
222
+ Rules:
223
+
224
+ - `fetch = FetchType.LAZY` is the default for `@OneToMany` — make it explicit
225
+ anyway
226
+ - `CascadeType.ALL` only when the child lifecycle is completely owned by the
227
+ parent
228
+ - `orphanRemoval = true` when removing an item from the collection should delete
229
+ the row
230
+
231
+ ### Many-to-One
232
+
233
+ ```kotlin
234
+ @ManyToOne(fetch = FetchType.LAZY) // LAZY — never EAGER
235
+ @JoinColumn(name = "user_id", nullable = false)
236
+ val user: User
237
+ ```
238
+
239
+ `FetchType.EAGER` on `@ManyToOne` causes N+1 problems. Always use `LAZY` and
240
+ fetch eagerly with `JOIN FETCH` when needed.
241
+
242
+ ### Many-to-Many
243
+
244
+ Do not use `@ManyToMany` directly. Use a join entity with explicit fields.
245
+
246
+ ```kotlin
247
+ // bad — opaque join table, no room for additional fields
248
+ @ManyToMany
249
+ @JoinTable(name = "user_roles")
250
+ val roles: MutableSet<Role>
251
+
252
+ // good — explicit join entity
253
+ @Entity
254
+ @Table(name = "user_roles")
255
+ class UserRole(
256
+ @Id @GeneratedValue(strategy = GenerationType.UUID)
257
+ val id: UUID = UUID.randomUUID(),
258
+
259
+ @ManyToOne(fetch = FetchType.LAZY)
260
+ @JoinColumn(name = "user_id", nullable = false)
261
+ val user: User,
262
+
263
+ @ManyToOne(fetch = FetchType.LAZY)
264
+ @JoinColumn(name = "role_id", nullable = false)
265
+ val role: Role,
266
+
267
+ @Column(name = "assigned_at", nullable = false)
268
+ val assignedAt: Instant = Instant.now()
269
+ )
270
+ ```
271
+
272
+ The join entity approach is more flexible — you can add fields like
273
+ `assignedAt`, `assignedBy`, and query through a repository.
274
+
275
+ ## N+1 Problem
276
+
277
+ The N+1 problem occurs when loading a collection results in one query for the
278
+ parent and N additional queries for each child, where N is the number of
279
+ parents.
280
+
281
+ **Detection:**
282
+
283
+ Enable SQL logging in tests:
284
+
285
+ ```yaml
286
+ # application-test.yml
287
+ spring:
288
+ jpa:
289
+ show-sql: true
290
+ properties:
291
+ hibernate:
292
+ format_sql: true
293
+ logging:
294
+ level:
295
+ org.hibernate.SQL: DEBUG
296
+ org.hibernate.type.descriptor.sql.BasicBinder: TRACE
297
+ ```
298
+
299
+ Count the queries. If you see `SELECT * FROM order_items WHERE order_id = ?`
300
+ executed once per order, you have N+1.
301
+
302
+ **Fix with `@EntityGraph`:**
303
+
304
+ ```kotlin
305
+ @Repository
306
+ interface OrderRepository : JpaRepository<Order, UUID> {
307
+
308
+ @EntityGraph(attributePaths = ["items", "items.product"])
309
+ fun findWithItemsById(id: UUID): Optional<Order>
310
+
311
+ @EntityGraph(attributePaths = ["items"])
312
+ fun findAllWithItems(): List<Order>
313
+ }
314
+ ```
315
+
316
+ **Fix with JPQL JOIN FETCH:**
317
+
318
+ ```kotlin
319
+ @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
320
+ fun findByIdWithItems(@Param("id") id: UUID): Optional<Order>
321
+
322
+ @Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.items WHERE o.status = :status")
323
+ fun findByStatusWithItems(@Param("status") status: OrderStatus): List<Order>
324
+ ```
325
+
326
+ Use `DISTINCT` with `JOIN FETCH` on collections to avoid duplicate parent rows
327
+ in the result.
328
+
329
+ **Rule:** never access a lazy collection outside a transaction. This causes
330
+ `LazyInitializationException`. If a service method needs related data, fetch it
331
+ within the same transaction using `@EntityGraph` or `JOIN FETCH`.
332
+
333
+ ## Projections for Read Queries
334
+
335
+ When you only need a subset of fields, do not load the full entity. Use
336
+ projections.
337
+
338
+ **Interface projection:**
339
+
340
+ ```kotlin
341
+ interface UserSummary {
342
+ val id: UUID
343
+ val email: String
344
+ val name: String
345
+ }
346
+
347
+ @Repository
348
+ interface UserRepository : JpaRepository<User, UUID> {
349
+ fun findAllProjectedBy(): List<UserSummary>
350
+ fun findProjectedById(id: UUID): UserSummary?
351
+ }
352
+ ```
353
+
354
+ **DTO projection with constructor expression:**
355
+
356
+ ```kotlin
357
+ data class UserDto(val id: UUID, val email: String)
358
+
359
+ @Query("SELECT new com.example.user.dto.UserDto(u.id, u.email) FROM User u WHERE u.active = true")
360
+ fun findActiveUserDtos(): List<UserDto>
361
+ ```
362
+
363
+ Use projections for list endpoints and reports. Load full entities only when you
364
+ need to modify them.
365
+
366
+ ## Flyway Migrations
367
+
368
+ ### Naming Convention
369
+
370
+ ```
371
+ V{timestamp}__{description}.sql
372
+ ```
373
+
374
+ Use a timestamp, not a sequential number, to avoid conflicts in parallel
375
+ branches.
376
+
377
+ ```
378
+ V20260507120000__create_users_table.sql
379
+ V20260507120001__add_users_email_index.sql
380
+ V20260508090000__add_orders_table.sql
381
+ ```
382
+
383
+ ### Migration Rules
384
+
385
+ - Never modify a migration that has already been applied in any environment
386
+ - Each migration must be forward-only — Flyway does not support automatic
387
+ rollbacks
388
+ - DDL changes and data migrations must be in separate files
389
+ - Destructive operations (DROP COLUMN, DROP TABLE) require a multi-step process:
390
+ 1. Migration 1: deploy code that no longer uses the column
391
+ 2. Migration 2: drop the column (safe once all instances are updated)
392
+
393
+ ### Schema Migration Example
394
+
395
+ ```sql
396
+ -- V20260507120000__create_users_table.sql
397
+ CREATE TABLE users (
398
+ id UUID NOT NULL PRIMARY KEY,
399
+ email VARCHAR(255) NOT NULL,
400
+ name VARCHAR(100) NOT NULL,
401
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
402
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
403
+ deleted_at TIMESTAMPTZ
404
+ );
405
+
406
+ CREATE UNIQUE INDEX idx_users_email ON users (email) WHERE deleted_at IS NULL;
407
+ ```
408
+
409
+ ### Rollback Strategy
410
+
411
+ Document rollback SQL in comments at the top of each migration file:
412
+
413
+ ```sql
414
+ -- V20260507120001__add_user_role_column.sql
415
+ -- Rollback: ALTER TABLE users DROP COLUMN role;
416
+
417
+ ALTER TABLE users ADD COLUMN role VARCHAR(50) NOT NULL DEFAULT 'USER';
418
+ ```
419
+
420
+ Automated rollback is not used. Manual rollback is applied only in emergencies.
421
+
422
+ ## PostgreSQL Specifics
423
+
424
+ ### Column Types
425
+
426
+ | Use case | PostgreSQL type | JPA mapping |
427
+ | ------------------------------- | --------------- | ------------------------------------- |
428
+ | Short text, known max length | `VARCHAR(n)` | `@Column(length = n)` |
429
+ | Long text, no known max | `TEXT` | `@Column(columnDefinition = "TEXT")` |
430
+ | Structured semi-structured data | `JSONB` | `@Column(columnDefinition = "JSONB")` |
431
+ | Timestamps with timezone | `TIMESTAMPTZ` | `Instant` |
432
+ | UUIDs | `UUID` | `UUID` |
433
+ | Money/currency | `NUMERIC(19,4)` | `BigDecimal` |
434
+
435
+ Use `TIMESTAMPTZ` (with timezone), not `TIMESTAMP`. Store all timestamps in UTC.
436
+
437
+ ### JSONB
438
+
439
+ Use `JSONB` for semi-structured data that does not warrant its own table.
440
+
441
+ ```kotlin
442
+ @Column(name = "metadata", columnDefinition = "JSONB")
443
+ @Type(JsonType::class) // requires hypersistence-utils dependency
444
+ var metadata: Map<String, Any> = emptyMap()
445
+ ```
446
+
447
+ JSONB is indexable. JSON is not. Always use JSONB.
448
+
449
+ ### Partial Indexes
450
+
451
+ Partial indexes cannot be expressed through JPA annotations. Use a migration:
452
+
453
+ ```sql
454
+ -- For soft-deleted tables: only index active (non-deleted) rows
455
+ CREATE UNIQUE INDEX idx_users_email_active ON users (email) WHERE deleted_at IS NULL;
456
+
457
+ -- For status-filtered queries
458
+ CREATE INDEX idx_orders_pending ON orders (created_at) WHERE status = 'PENDING';
459
+ ```
460
+
461
+ ### Native Queries with RETURNING
462
+
463
+ Use `RETURNING` to avoid an extra `SELECT` after an `INSERT` or `UPDATE`:
464
+
465
+ ```kotlin
466
+ @Modifying
467
+ @Query(
468
+ value = "INSERT INTO audit_log (user_id, action, created_at) VALUES (:userId, :action, NOW()) RETURNING id",
469
+ nativeQuery = true
470
+ )
471
+ fun insertAndReturnId(
472
+ @Param("userId") userId: UUID,
473
+ @Param("action") action: String
474
+ ): UUID
475
+ ```
476
+
477
+ ### Complex Queries
478
+
479
+ Use native queries when JPQL cannot express what you need:
480
+
481
+ ```kotlin
482
+ @Query(
483
+ value = """
484
+ SELECT u.id, u.email, COUNT(o.id) as order_count
485
+ FROM users u
486
+ LEFT JOIN orders o ON o.user_id = u.id AND o.deleted_at IS NULL
487
+ WHERE u.deleted_at IS NULL
488
+ GROUP BY u.id, u.email
489
+ HAVING COUNT(o.id) > :minOrders
490
+ ORDER BY order_count DESC
491
+ LIMIT :limit
492
+ """,
493
+ nativeQuery = true
494
+ )
495
+ fun findActiveUsersWithMinOrders(
496
+ @Param("minOrders") minOrders: Int,
497
+ @Param("limit") limit: Int
498
+ ): List<Map<String, Any>>
499
+ ```
500
+
501
+ ## Query Performance
502
+
503
+ ### Pagination
504
+
505
+ Always paginate. Never call `findAll()` on a large table.
506
+
507
+ ```kotlin
508
+ @Repository
509
+ interface UserRepository : JpaRepository<User, UUID> {
510
+ fun findAll(pageable: Pageable): Page<UserSummary>
511
+ fun findByRole(role: UserRole, pageable: Pageable): Page<UserSummary>
512
+ }
513
+
514
+ // In service
515
+ fun list(page: Int, size: Int): Page<UserSummary> {
516
+ val pageable = PageRequest.of(page, size, Sort.by("createdAt").descending())
517
+ return userRepository.findAll(pageable)
518
+ }
519
+ ```
520
+
521
+ Use `Slice<T>` instead of `Page<T>` when you do not need the total count
522
+ (cheaper — no COUNT query).
523
+
524
+ ### QueryHints for Fetch Control
525
+
526
+ ```kotlin
527
+ @QueryHints(
528
+ QueryHint(name = HINT_FETCHGRAPH, value = "User.withOrders")
529
+ )
530
+ fun findById(id: UUID): Optional<User>
531
+ ```
532
+
533
+ Requires a named entity graph:
534
+
535
+ ```kotlin
536
+ @Entity
537
+ @NamedEntityGraph(
538
+ name = "User.withOrders",
539
+ attributeNodes = [NamedAttributeNode("orders")]
540
+ )
541
+ class User(...)
542
+ ```
543
+
544
+ ## Testing
545
+
546
+ ### Unit — @DataJpaTest with H2
547
+
548
+ Fast. No server required.
549
+
550
+ ```kotlin
551
+ @DataJpaTest
552
+ class UserRepositoryTest {
553
+
554
+ @Autowired
555
+ private lateinit var userRepository: UserRepository
556
+
557
+ @Test
558
+ fun `should find user by email`() {
559
+ val saved = userRepository.save(User(email = "test@example.com", name = "Test User"))
560
+ val found = userRepository.findByEmail("test@example.com")
561
+ assertThat(found).isNotNull
562
+ assertThat(found?.id).isEqualTo(saved.id)
563
+ }
564
+
565
+ @Test
566
+ fun `should not find deleted user by email`() {
567
+ val user = userRepository.save(User(email = "deleted@example.com", name = "Gone"))
568
+ userRepository.delete(user) // triggers @SQLDelete — sets deleted_at
569
+ val found = userRepository.findByEmail("deleted@example.com")
570
+ assertThat(found).isNull()
571
+ }
572
+ }
573
+ ```
574
+
575
+ ### Integration — Testcontainers with PostgreSQL
576
+
577
+ Real database. Use for migrations, JSONB, partial indexes, and native queries.
578
+
579
+ ```kotlin
580
+ @SpringBootTest
581
+ @Testcontainers
582
+ class UserRepositoryIntegrationTest {
583
+
584
+ companion object {
585
+ @Container
586
+ @JvmStatic
587
+ val postgres = PostgreSQLContainer<Nothing>("postgres:16").apply {
588
+ withDatabaseName("testdb")
589
+ withUsername("test")
590
+ withPassword("test")
591
+ }
592
+
593
+ @DynamicPropertySource
594
+ @JvmStatic
595
+ fun registerProperties(registry: DynamicPropertyRegistry) {
596
+ registry.add("spring.datasource.url", postgres::getJdbcUrl)
597
+ registry.add("spring.datasource.username", postgres::getUsername)
598
+ registry.add("spring.datasource.password", postgres::getPassword)
599
+ }
600
+ }
601
+
602
+ @Autowired
603
+ private lateinit var userRepository: UserRepository
604
+
605
+ @Test
606
+ fun `should enforce unique email constraint at db level`() {
607
+ userRepository.save(User(email = "dup@example.com", name = "First"))
608
+ assertThrows<DataIntegrityViolationException> {
609
+ userRepository.save(User(email = "dup@example.com", name = "Second"))
610
+ }
611
+ }
612
+ }
613
+ ```
614
+
615
+ Use Testcontainers for:
616
+
617
+ - Flyway migration validation
618
+ - PostgreSQL-specific features (JSONB, partial indexes, native queries)
619
+ - Constraint enforcement (unique, foreign key, check constraints)
620
+ - Query performance checks that differ between H2 and PostgreSQL
621
+
622
+ Use `@DataJpaTest` with H2 for:
623
+
624
+ - Derived query methods
625
+ - Simple CRUD operations
626
+ - JPQL queries with standard JPA behavior