cc-codeconductor 0.2.1

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 (93) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +319 -0
  3. package/dist/index.js +12741 -0
  4. package/package.json +51 -0
  5. package/policy.yml +124 -0
  6. package/presets/claude/CLAUDE.md +598 -0
  7. package/presets/claude/commands/cc/feature.md +115 -0
  8. package/presets/claude/commands/cc/fix.md +121 -0
  9. package/presets/claude/commands/cc/refactor.md +148 -0
  10. package/presets/claude/commands/cc/review.md +126 -0
  11. package/presets/claude/commands/cc/tdd-cycle.md +226 -0
  12. package/presets/claude/commands/cc/test-plan.md +138 -0
  13. package/presets/claude/settings.json +37 -0
  14. package/presets/claude/skills/api-versioning/SKILL.md +389 -0
  15. package/presets/claude/skills/django-orm/SKILL.md +455 -0
  16. package/presets/claude/skills/django-testing/SKILL.md +409 -0
  17. package/presets/claude/skills/jpa-postgres/SKILL.md +618 -0
  18. package/presets/claude/skills/python/SKILL.md +606 -0
  19. package/presets/claude/skills/python-django-stack/SKILL.md +492 -0
  20. package/presets/claude/skills/python-fastapi-stack/SKILL.md +457 -0
  21. package/presets/claude/skills/spring-boot-feature/SKILL.md +558 -0
  22. package/presets/claude/skills/spring-boot-kotlin/SKILL.md +400 -0
  23. package/presets/claude/skills/sqlalchemy/SKILL.md +466 -0
  24. package/presets/claude/skills/testing-strategy/SKILL.md +479 -0
  25. package/presets/codex/AGENTS.md +883 -0
  26. package/presets/codex/README.md +102 -0
  27. package/presets/codex/skills/api-versioning/SKILL.md +389 -0
  28. package/presets/codex/skills/django-orm/SKILL.md +455 -0
  29. package/presets/codex/skills/django-testing/SKILL.md +409 -0
  30. package/presets/codex/skills/jpa-postgres/SKILL.md +618 -0
  31. package/presets/codex/skills/python/SKILL.md +606 -0
  32. package/presets/codex/skills/python-django-stack/SKILL.md +492 -0
  33. package/presets/codex/skills/python-fastapi-stack/SKILL.md +457 -0
  34. package/presets/codex/skills/spring-boot-feature/SKILL.md +558 -0
  35. package/presets/codex/skills/spring-boot-kotlin/SKILL.md +400 -0
  36. package/presets/codex/skills/sqlalchemy/SKILL.md +466 -0
  37. package/presets/codex/skills/testing-strategy/SKILL.md +479 -0
  38. package/presets/opencode/README.md +191 -0
  39. package/presets/opencode/agents/architect.md +133 -0
  40. package/presets/opencode/agents/docs.md +113 -0
  41. package/presets/opencode/agents/implementer.md +116 -0
  42. package/presets/opencode/agents/orchestrator.md +248 -0
  43. package/presets/opencode/agents/repo-explorer.md +119 -0
  44. package/presets/opencode/agents/reviewer.md +130 -0
  45. package/presets/opencode/agents/task-coach.md +103 -0
  46. package/presets/opencode/agents/tester.md +269 -0
  47. package/presets/opencode/commands/cc-feature.md +114 -0
  48. package/presets/opencode/commands/cc-fix.md +123 -0
  49. package/presets/opencode/commands/cc-refactor.md +148 -0
  50. package/presets/opencode/commands/cc-review.md +141 -0
  51. package/presets/opencode/commands/cc-tdd-cycle.md +225 -0
  52. package/presets/opencode/commands/cc-test-plan.md +144 -0
  53. package/presets/opencode/opencode.jsonc +89 -0
  54. package/presets/opencode/prompts/v0.1.0/architect.md +213 -0
  55. package/presets/opencode/prompts/v0.1.0/docs.md +181 -0
  56. package/presets/opencode/prompts/v0.1.0/implementer.md +154 -0
  57. package/presets/opencode/prompts/v0.1.0/orchestrator.md +169 -0
  58. package/presets/opencode/prompts/v0.1.0/repo-explorer.md +102 -0
  59. package/presets/opencode/prompts/v0.1.0/reviewer.md +183 -0
  60. package/presets/opencode/prompts/v0.1.0/task-coach.md +142 -0
  61. package/presets/opencode/prompts/v0.1.0/tester.md +160 -0
  62. package/presets/opencode/prompts/v0.2.0/architect.md +219 -0
  63. package/presets/opencode/prompts/v0.2.0/docs.md +187 -0
  64. package/presets/opencode/prompts/v0.2.0/implementer.md +160 -0
  65. package/presets/opencode/prompts/v0.2.0/orchestrator.md +238 -0
  66. package/presets/opencode/prompts/v0.2.0/repo-explorer.md +108 -0
  67. package/presets/opencode/prompts/v0.2.0/reviewer.md +190 -0
  68. package/presets/opencode/prompts/v0.2.0/task-coach.md +153 -0
  69. package/presets/opencode/prompts/v0.2.0/tester.md +249 -0
  70. package/presets/opencode/skills/api-versioning/SKILL.md +388 -0
  71. package/presets/opencode/skills/astro/SKILL.md +313 -0
  72. package/presets/opencode/skills/code-review/SKILL.md +202 -0
  73. package/presets/opencode/skills/django-orm/SKILL.md +455 -0
  74. package/presets/opencode/skills/django-testing/SKILL.md +408 -0
  75. package/presets/opencode/skills/django-uv/SKILL.md +400 -0
  76. package/presets/opencode/skills/jpa-postgres/SKILL.md +617 -0
  77. package/presets/opencode/skills/nextjs-typescript/SKILL.md +385 -0
  78. package/presets/opencode/skills/python/SKILL.md +605 -0
  79. package/presets/opencode/skills/python-django-stack/SKILL.md +491 -0
  80. package/presets/opencode/skills/python-fastapi-stack/SKILL.md +456 -0
  81. package/presets/opencode/skills/security/SKILL.md +376 -0
  82. package/presets/opencode/skills/spring-boot-feature/SKILL.md +557 -0
  83. package/presets/opencode/skills/spring-boot-kotlin/SKILL.md +399 -0
  84. package/presets/opencode/skills/spring-boot-testing-strategy/SKILL.md +470 -0
  85. package/presets/opencode/skills/sqlalchemy/SKILL.md +465 -0
  86. package/presets/opencode/skills/testing-tdd/SKILL.md +586 -0
  87. package/src/presets/council/council.yml +59 -0
  88. package/src/presets/manifests/claude.yml +23 -0
  89. package/src/presets/manifests/codex.yml +12 -0
  90. package/src/presets/manifests/opencode.yml +18 -0
  91. package/src/presets/models/claude.yml +37 -0
  92. package/src/presets/models/codex.yml +38 -0
  93. package/src/presets/models/opencode.yml +37 -0
@@ -0,0 +1,470 @@
1
+ ---
2
+ id: spring-boot-testing-strategy
3
+ version: 1.0.0
4
+ name: Spring Boot Testing Strategy
5
+ description: >
6
+ Provides expert knowledge of the testing pyramid, test design principles, MockK patterns, and integration testing conventions for Spring Boot + Kotlin projects.
7
+
8
+ compatibility:
9
+ tools: [claude, codex, opencode]
10
+ stacks:
11
+ languages: [kotlin, java]
12
+ frameworks: [spring-boot, kotest, mockk, junit5, mockk, testcontainers, assertj]
13
+ databases: [postgresql, sqlite, h2]
14
+
15
+ risk:
16
+ level: low
17
+ can_execute_shell: false
18
+ can_modify_files: true
19
+ requires_network: false
20
+
21
+ inputs: []
22
+
23
+ outputs: []
24
+
25
+ quality:
26
+ reviewed_by: codeconductor-core
27
+ version: 0.1.0
28
+ ---
29
+
30
+ # Testing Strategy
31
+
32
+ ## Testing Pyramid
33
+
34
+ ```text
35
+ /\
36
+ / \
37
+ / E2E\ 10% — full API, happy path + main error cases
38
+ /------\
39
+ / Integ \ 20% — components with real dependencies (DB, HTTP)
40
+ /----------\
41
+ / Unit \ 70% — isolated, mocked dependencies, fast
42
+ /______________\
43
+ ```
44
+
45
+ Unit tests are the foundation. They are fast, deterministic, and cheap to run.
46
+ Integration tests validate that components work together. E2E tests validate
47
+ that the system works end to end — keep them minimal.
48
+
49
+ If you find yourself writing more integration tests than unit tests, the code
50
+ under test has too many responsibilities bundled together.
51
+
52
+ ## Test Naming Convention
53
+
54
+ Format: `should [expected behavior] when [condition]`
55
+
56
+ ```kotlin
57
+ @Test
58
+ fun `should return user when found by id`() { ... }
59
+
60
+ @Test
61
+ fun `should return 404 when user does not exist`() { ... }
62
+
63
+ @Test
64
+ fun `should throw ConflictException when email already exists`() { ... }
65
+
66
+ @Test
67
+ fun `should not return deleted users in list`() { ... }
68
+ ```
69
+
70
+ Group related tests with `@Nested`:
71
+
72
+ ```kotlin
73
+ @ExtendWith(MockKExtension::class)
74
+ class UserServiceTest {
75
+
76
+ @Nested
77
+ inner class GetById {
78
+ @Test
79
+ fun `should return user when found`() { ... }
80
+
81
+ @Test
82
+ fun `should return NotFound when user does not exist`() { ... }
83
+ }
84
+
85
+ @Nested
86
+ inner class Create {
87
+ @Test
88
+ fun `should create and return user when email is unique`() { ... }
89
+
90
+ @Test
91
+ fun `should throw ConflictException when email already exists`() { ... }
92
+ }
93
+ }
94
+ ```
95
+
96
+ ## Unit Test Structure (AAA)
97
+
98
+ Every test follows Arrange → Act → Assert. Use blank lines to separate each
99
+ phase.
100
+
101
+ ```kotlin
102
+ @Test
103
+ fun `should return user when found by id`() {
104
+ // Arrange
105
+ val userId = UUID.randomUUID()
106
+ val user = User(id = userId, email = "user@example.com", name = "Test User")
107
+ every { userRepository.findById(userId) } returns Optional.of(user)
108
+
109
+ // Act
110
+ val result = userService.getById(userId)
111
+
112
+ // Assert
113
+ assertThat(result).isInstanceOf(UserResult.Found::class.java)
114
+ val found = result as UserResult.Found
115
+ assertThat(found.user.id).isEqualTo(userId)
116
+ verify(exactly = 1) { userRepository.findById(userId) }
117
+ }
118
+ ```
119
+
120
+ No inline comments between phases once the structure is clear. The blank lines
121
+ are enough.
122
+
123
+ ## What NOT to Test
124
+
125
+ **Framework wiring.** Spring handles dependency injection. Do not write tests
126
+ that verify `@Autowired` works.
127
+
128
+ **JPA mapping.** Do not test that `@Column(name = "email")` maps to the right
129
+ column. That is Hibernate's job.
130
+
131
+ **Trivial getters and setters.** A data class property has no logic. There is
132
+ nothing to test.
133
+
134
+ **Private methods directly.** Private methods are implementation details. Test
135
+ them through the public behavior that uses them. If a private method is complex
136
+ enough to need its own test, it should be extracted into a separate class.
137
+
138
+ **Implementation, not behavior.** Tests that verify HOW something is done (mock
139
+ call order, internal state) are brittle. Test WHAT the output or side effect is.
140
+
141
+ ```kotlin
142
+ // bad — tests implementation detail
143
+ verify { userRepository.findById(any()) }
144
+ verify { cacheService.put(any(), any()) }
145
+ verify(ordering = Ordering.ORDERED) { // this is too coupled to internals
146
+ userRepository.findById(userId)
147
+ cacheService.put(userId, user)
148
+ }
149
+
150
+ // good — tests observable behavior
151
+ assertThat(result).isEqualTo(expectedUser)
152
+ ```
153
+
154
+ ## MockK Patterns
155
+
156
+ ### Basic Setup
157
+
158
+ ```kotlin
159
+ @ExtendWith(MockKExtension::class)
160
+ class UserServiceTest {
161
+
162
+ @MockK
163
+ private lateinit var userRepository: UserRepository
164
+
165
+ @MockK
166
+ private lateinit var emailService: EmailService
167
+
168
+ private lateinit var userService: UserService
169
+
170
+ @BeforeEach
171
+ fun setUp() {
172
+ userService = UserService(userRepository, emailService)
173
+ }
174
+ }
175
+ ```
176
+
177
+ ### Stubbing
178
+
179
+ ```kotlin
180
+ // Return value
181
+ every { userRepository.findById(userId) } returns Optional.of(user)
182
+
183
+ // Return null (for nullable return types)
184
+ every { userRepository.findByEmail(any()) } returns null
185
+
186
+ // Throw exception
187
+ every { userRepository.save(any()) } throws DataIntegrityViolationException("Duplicate")
188
+
189
+ // Return different values on successive calls
190
+ every { userRepository.findById(any()) } returnsMany listOf(Optional.of(user), Optional.empty())
191
+
192
+ // Answer with computation
193
+ every { userRepository.save(any()) } answers { firstArg() }
194
+ ```
195
+
196
+ ### Verification
197
+
198
+ ```kotlin
199
+ // Verify called exactly once with specific argument
200
+ verify(exactly = 1) { userRepository.findById(userId) }
201
+
202
+ // Verify called with any argument
203
+ verify { emailService.sendWelcome(any()) }
204
+
205
+ // Verify never called
206
+ verify(exactly = 0) { emailService.sendWelcome(any()) }
207
+
208
+ // Verify called with specific argument
209
+ verify { emailService.sendWelcome(match { it.email == "user@example.com" }) }
210
+ ```
211
+
212
+ ### Relaxed Mocks
213
+
214
+ ```kotlin
215
+ // relaxed = true — returns default values (null, 0, false, empty list)
216
+ // Use only when you do not care about the return value of any method
217
+ val logger = mockk<Logger>(relaxed = true)
218
+ ```
219
+
220
+ Do not use `relaxed = true` on mocks where you need to verify behavior — a
221
+ relaxed mock does not fail on unexpected calls, which can hide bugs.
222
+
223
+ ### Coroutines
224
+
225
+ ```kotlin
226
+ // Stubbing suspend functions
227
+ coEvery { userRepository.findById(userId) } returns user
228
+
229
+ // Verifying suspend functions
230
+ coVerify(exactly = 1) { userRepository.findById(userId) }
231
+ ```
232
+
233
+ ### Argument Matchers
234
+
235
+ ```kotlin
236
+ // Any value
237
+ every { service.process(any()) } returns result
238
+
239
+ // Specific type
240
+ every { service.process(any<UserRequest>()) } returns result
241
+
242
+ // Custom predicate
243
+ every { service.notify(match { it.email.endsWith("@example.com") }) } just Runs
244
+
245
+ // Capture for assertion
246
+ val slot = slot<User>()
247
+ every { userRepository.save(capture(slot)) } returns savedUser
248
+ // ... trigger the action ...
249
+ assertThat(slot.captured.email).isEqualTo("expected@example.com")
250
+ ```
251
+
252
+ ## Integration Test Patterns
253
+
254
+ ### Controller Tests with MockMvc
255
+
256
+ ```kotlin
257
+ @SpringBootTest
258
+ @AutoConfigureMockMvc
259
+ class UserControllerTest {
260
+
261
+ @Autowired
262
+ private lateinit var mockMvc: MockMvc
263
+
264
+ @Autowired
265
+ private lateinit var objectMapper: ObjectMapper
266
+
267
+ @Test
268
+ fun `should return 200 with user when found`() {
269
+ mockMvc.perform(
270
+ get("/api/v1/users/${existingUser.id}")
271
+ .contentType(MediaType.APPLICATION_JSON)
272
+ )
273
+ .andExpect(status().isOk)
274
+ .andExpect(jsonPath("$.email").value(existingUser.email))
275
+ .andExpect(jsonPath("$.id").value(existingUser.id.toString()))
276
+ }
277
+
278
+ @Test
279
+ fun `should return 400 when request body is invalid`() {
280
+ val invalidRequest = mapOf("email" to "not-an-email")
281
+
282
+ mockMvc.perform(
283
+ post("/api/v1/users")
284
+ .contentType(MediaType.APPLICATION_JSON)
285
+ .content(objectMapper.writeValueAsString(invalidRequest))
286
+ )
287
+ .andExpect(status().isBadRequest)
288
+ .andExpect(jsonPath("$.code").value("VALIDATION_ERROR"))
289
+ }
290
+
291
+ @Test
292
+ fun `should return 404 when user is not found`() {
293
+ mockMvc.perform(get("/api/v1/users/${UUID.randomUUID()}"))
294
+ .andExpect(status().isNotFound)
295
+ }
296
+ }
297
+ ```
298
+
299
+ `@AutoConfigureMockMvc` configures MockMvc without starting a real HTTP server.
300
+ The test is faster than `RANDOM_PORT` but still tests the full Spring MVC stack.
301
+
302
+ Use `RANDOM_PORT` only when you need real HTTP (e.g., testing HTTP client
303
+ behavior, WebSockets, or SSE).
304
+
305
+ ### Database Tests with Testcontainers
306
+
307
+ ```kotlin
308
+ @SpringBootTest
309
+ @Testcontainers
310
+ class UserRepositoryIntegrationTest {
311
+
312
+ companion object {
313
+ @Container
314
+ @JvmStatic
315
+ val postgres = PostgreSQLContainer<Nothing>("postgres:16").apply {
316
+ withDatabaseName("testdb")
317
+ withUsername("test")
318
+ withPassword("test")
319
+ }
320
+
321
+ @DynamicPropertySource
322
+ @JvmStatic
323
+ fun overrideProperties(registry: DynamicPropertyRegistry) {
324
+ registry.add("spring.datasource.url", postgres::getJdbcUrl)
325
+ registry.add("spring.datasource.username", postgres::getUsername)
326
+ registry.add("spring.datasource.password", postgres::getPassword)
327
+ }
328
+ }
329
+
330
+ @Autowired
331
+ private lateinit var userRepository: UserRepository
332
+
333
+ @Test
334
+ fun `should enforce unique email at database level`() {
335
+ userRepository.save(User(email = "unique@example.com", name = "First"))
336
+ assertThrows<DataIntegrityViolationException> {
337
+ userRepository.save(User(email = "unique@example.com", name = "Second"))
338
+ }
339
+ }
340
+ }
341
+ ```
342
+
343
+ Share the container across test classes using a base class with `@Container` on
344
+ a static field. Testcontainers reuses the same container if the configuration is
345
+ identical.
346
+
347
+ ### Seed Data with @Sql
348
+
349
+ ```kotlin
350
+ @SpringBootTest
351
+ @AutoConfigureMockMvc
352
+ @Sql("/test-data/users.sql") // runs before each test in this class
353
+ class UserControllerTest { ... }
354
+
355
+ // Or per-test
356
+ @Test
357
+ @Sql("/test-data/users-with-orders.sql")
358
+ fun `should return orders when user has orders`() { ... }
359
+ ```
360
+
361
+ SQL files go in `src/test/resources/test-data/`. Use descriptive names that
362
+ explain what state they create, not what feature they serve.
363
+
364
+ ## Test Data Factories
365
+
366
+ Never hardcode UUIDs or share mutable state between tests.
367
+
368
+ ```kotlin
369
+ object UserTestFactory {
370
+
371
+ fun create(
372
+ id: UUID = UUID.randomUUID(),
373
+ email: String = "user-${UUID.randomUUID()}@example.com",
374
+ name: String = "Test User",
375
+ role: UserRole = UserRole.USER
376
+ ): User = User(
377
+ id = id,
378
+ email = email,
379
+ name = name,
380
+ role = role
381
+ )
382
+
383
+ fun createRequest(
384
+ email: String = "user-${UUID.randomUUID()}@example.com",
385
+ name: String = "Test User"
386
+ ): CreateUserRequest = CreateUserRequest(
387
+ email = email,
388
+ name = name
389
+ )
390
+
391
+ fun createAdmin(): User = create(role = UserRole.ADMIN)
392
+
393
+ fun createList(count: Int): List<User> = (1..count).map { create() }
394
+ }
395
+ ```
396
+
397
+ Usage in tests:
398
+
399
+ ```kotlin
400
+ val user = UserTestFactory.create(email = "specific@example.com")
401
+ val admin = UserTestFactory.createAdmin()
402
+ val users = UserTestFactory.createList(5)
403
+ ```
404
+
405
+ Default values use `UUID.randomUUID()` to ensure tests do not share state
406
+ through hardcoded IDs or emails.
407
+
408
+ ## When Tests Are Required
409
+
410
+ **New behavior.** Every new feature requires at minimum one unit test and one
411
+ integration test covering the happy path. Edge cases and error paths require
412
+ additional unit tests.
413
+
414
+ **Bug fix.** Write a regression test that reproduces the bug before fixing it.
415
+ The test must fail before the fix and pass after. This prevents the bug from
416
+ reappearing silently.
417
+
418
+ ```kotlin
419
+ // Example: bug where findByEmail returned deleted users
420
+ @Test
421
+ fun `should not return deleted user — regression for bug #42`() {
422
+ val user = userRepository.save(UserTestFactory.create(email = "gone@example.com"))
423
+ userRepository.delete(user)
424
+ val result = userRepository.findByEmail("gone@example.com")
425
+ assertThat(result).isNull()
426
+ }
427
+ ```
428
+
429
+ **Refactor.** Do not write new tests. The existing tests must pass unchanged. If
430
+ they do not, the refactor changed behavior — that is a bug.
431
+
432
+ **High-risk paths.** Security, payment processing, data migrations, and
433
+ permission checks require contract tests in addition to unit and integration
434
+ tests. These paths have severe consequences if broken.
435
+
436
+ ## CI Integration
437
+
438
+ Tests must run in CI on every pull request. The pipeline must fail if any test
439
+ fails.
440
+
441
+ Recommended test execution order:
442
+
443
+ 1. Unit tests (fastest — fail fast)
444
+ 2. Integration tests with H2 (`@DataJpaTest`)
445
+ 3. Integration tests with Testcontainers (slowest — run last)
446
+
447
+ Separate test tasks in Gradle:
448
+
449
+ ```kotlin
450
+ // build.gradle.kts
451
+ tasks.register<Test>("integrationTest") {
452
+ description = "Runs integration tests"
453
+ group = "verification"
454
+ useJUnitPlatform {
455
+ includeTags("integration")
456
+ }
457
+ shouldRunAfter(tasks.test)
458
+ }
459
+ ```
460
+
461
+ Tag integration tests:
462
+
463
+ ```kotlin
464
+ @Tag("integration")
465
+ @SpringBootTest
466
+ class UserRepositoryIntegrationTest { ... }
467
+ ```
468
+
469
+ Do not mix unit and integration test tags in the same class. A test class is
470
+ either one or the other.