cc-codeconductor 1.1.0 → 1.2.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.
- package/README.md +4 -2
- package/dist/core/verification/verification-runner.d.ts +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1413 -309
- package/dist/library.js +29 -1
- package/dist/validation/schemas.d.ts +97 -26
- package/package.json +1 -1
- package/presets/agy/AGENTS.md +10 -9
- package/presets/agy/hooks.json +2 -2
- package/presets/agy/scripts/invoke-hook.cjs +115 -0
- package/presets/agy/skills/backlog/SKILL.md +40 -70
- package/presets/agy/skills/cc-spec-mutation/SKILL.md +165 -0
- package/presets/agy/skills/cc-tdd-cycle/SKILL.md +3 -0
- package/presets/agy/skills/evaluation/SKILL.md +61 -2
- package/presets/agy/skills/openspec/SKILL.md +49 -19
- package/presets/agy/skills/testing-tdd/SKILL.md +53 -0
- package/presets/agy/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/agy/workflows/cc-api-contract.md +14 -0
- package/presets/agy/workflows/cc-db-migration.md +14 -0
- package/presets/agy/workflows/cc-feature.md +18 -0
- package/presets/agy/workflows/cc-fix.md +14 -0
- package/presets/agy/workflows/cc-iterative.md +14 -0
- package/presets/agy/workflows/cc-openspec.md +14 -0
- package/presets/agy/workflows/cc-scorecard.md +2 -0
- package/presets/agy/workflows/cc-spec-mutation.md +191 -0
- package/presets/agy/workflows/cc-tdd-cycle.md +14 -0
- package/presets/claude/commands/cc/api-contract.md +14 -0
- package/presets/claude/commands/cc/db-migration.md +14 -0
- package/presets/claude/commands/cc/feature.md +18 -0
- package/presets/claude/commands/cc/fix.md +17 -0
- package/presets/claude/commands/cc/iterative.md +14 -0
- package/presets/claude/commands/cc/openspec.md +14 -0
- package/presets/claude/commands/cc/review.md +3 -0
- package/presets/claude/commands/cc/scorecard.md +2 -0
- package/presets/claude/commands/cc/spec-mutation.md +190 -0
- package/presets/claude/commands/cc/tdd-cycle.md +17 -0
- package/presets/claude/settings.json +13 -11
- package/presets/claude/skills/backlog/SKILL.md +40 -70
- package/presets/claude/skills/evaluation/SKILL.md +47 -24
- package/presets/claude/skills/openspec/SKILL.md +46 -38
- package/presets/claude/skills/testing-tdd/SKILL.md +53 -0
- package/presets/claude/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/codex/AGENTS.md +16 -12
- package/presets/codex/skills/backlog/SKILL.md +61 -0
- package/presets/codex/skills/cc-api-contract/SKILL.md +87 -0
- package/presets/codex/skills/cc-backlog/SKILL.md +108 -0
- package/presets/codex/skills/cc-clarify/SKILL.md +36 -0
- package/presets/codex/skills/cc-council/SKILL.md +92 -0
- package/presets/codex/skills/cc-db-migration/SKILL.md +88 -0
- package/presets/codex/skills/cc-explore/SKILL.md +40 -0
- package/presets/codex/skills/cc-feature/SKILL.md +154 -0
- package/presets/codex/skills/cc-fix/SKILL.md +165 -0
- package/presets/codex/skills/cc-handoff/SKILL.md +45 -0
- package/presets/codex/skills/cc-iterative/SKILL.md +150 -0
- package/presets/codex/skills/cc-openspec/SKILL.md +191 -0
- package/presets/codex/skills/cc-pagespeed/SKILL.md +124 -0
- package/presets/codex/skills/cc-prototype/SKILL.md +42 -0
- package/presets/codex/skills/cc-refactor/SKILL.md +163 -0
- package/presets/codex/skills/cc-review/SKILL.md +152 -0
- package/presets/codex/skills/cc-scorecard/SKILL.md +82 -0
- package/presets/codex/skills/cc-security/SKILL.md +182 -0
- package/presets/codex/skills/cc-spec-mutation/SKILL.md +192 -0
- package/presets/codex/skills/cc-tdd-cycle/SKILL.md +266 -0
- package/presets/codex/skills/cc-test-plan/SKILL.md +153 -0
- package/presets/codex/skills/cc-triage/SKILL.md +38 -0
- package/presets/codex/skills/evaluation/SKILL.md +65 -0
- package/presets/codex/skills/openspec/SKILL.md +66 -0
- package/presets/codex/skills/testing-tdd/SKILL.md +53 -0
- package/presets/codex/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/cursor/commands/cc/api-contract.md +14 -0
- package/presets/cursor/commands/cc/db-migration.md +14 -0
- package/presets/cursor/commands/cc/feature.md +18 -0
- package/presets/cursor/commands/cc/fix.md +17 -0
- package/presets/cursor/commands/cc/iterative.md +14 -0
- package/presets/cursor/commands/cc/openspec.md +14 -0
- package/presets/cursor/commands/cc/scorecard.md +2 -0
- package/presets/cursor/commands/cc/spec-mutation.md +190 -0
- package/presets/cursor/commands/cc/tdd-cycle.md +14 -0
- package/presets/cursor/skills/backlog/SKILL.md +40 -70
- package/presets/cursor/skills/evaluation/SKILL.md +61 -4
- package/presets/cursor/skills/openspec/SKILL.md +46 -36
- package/presets/cursor/skills/testing-tdd/SKILL.md +35 -574
- package/presets/cursor/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/gemini/commands/cc/api-contract.toml +82 -0
- package/presets/gemini/commands/cc/ask.toml +54 -0
- package/presets/gemini/commands/cc/backlog.toml +103 -0
- package/presets/gemini/commands/cc/clarify.toml +31 -0
- package/presets/gemini/commands/cc/council.toml +87 -0
- package/presets/gemini/commands/cc/db-migration.toml +83 -0
- package/presets/gemini/commands/cc/explore.toml +35 -0
- package/presets/gemini/commands/cc/feature.toml +153 -0
- package/presets/gemini/commands/cc/fix.toml +163 -0
- package/presets/gemini/commands/cc/handoff.toml +40 -0
- package/presets/gemini/commands/cc/iterative.toml +145 -0
- package/presets/gemini/commands/cc/openspec.toml +186 -0
- package/presets/gemini/commands/cc/pagespeed.toml +119 -0
- package/presets/gemini/commands/cc/prototype.toml +37 -0
- package/presets/gemini/commands/cc/refactor.toml +158 -0
- package/presets/gemini/commands/cc/review.toml +150 -0
- package/presets/gemini/commands/cc/scorecard.toml +77 -0
- package/presets/gemini/commands/cc/security.toml +177 -0
- package/presets/gemini/commands/cc/spec-mutation.toml +187 -0
- package/presets/gemini/commands/cc/tdd-cycle.toml +264 -0
- package/presets/gemini/commands/cc/test-plan.toml +148 -0
- package/presets/gemini/commands/cc/triage.toml +33 -0
- package/presets/opencode/README.md +24 -21
- package/presets/opencode/agents/architect.md +6 -0
- package/presets/opencode/agents/implementer.md +7 -0
- package/presets/opencode/agents/reviewer.md +6 -0
- package/presets/opencode/agents/tester.md +6 -0
- package/presets/opencode/commands/cc-api-contract.md +14 -0
- package/presets/opencode/commands/cc-db-migration.md +14 -0
- package/presets/opencode/commands/cc-feature.md +18 -0
- package/presets/opencode/commands/cc-fix.md +17 -0
- package/presets/opencode/commands/cc-iterative.md +14 -0
- package/presets/opencode/commands/cc-openspec.md +14 -0
- package/presets/opencode/commands/cc-scorecard.md +2 -0
- package/presets/opencode/commands/cc-spec-mutation.md +190 -0
- package/presets/opencode/commands/cc-tdd-cycle.md +14 -0
- package/presets/opencode/opencode.jsonc +1 -1
- package/presets/opencode/prompts/v1.0.0/architect.md +6 -0
- package/presets/opencode/prompts/v1.0.0/implementer.md +7 -0
- package/presets/opencode/prompts/v1.0.0/reviewer.md +6 -0
- package/presets/opencode/prompts/v1.0.0/tester.md +6 -0
- package/presets/opencode/skills/backlog/SKILL.md +40 -70
- package/presets/opencode/skills/evaluation/SKILL.md +61 -2
- package/presets/opencode/skills/openspec/SKILL.md +46 -34
- package/presets/opencode/skills/testing-tdd/SKILL.md +35 -574
- package/presets/opencode/skills/using-cc-skills/SKILL.md +48 -0
- package/presets/shared/__pycache__/mutation_runner.cpython-314.pyc +0 -0
- package/presets/shared/invoke-hook.cjs +115 -0
- package/presets/shared/mutation_runner.py +273 -0
- package/src/presets/manifests/agy.yml +2 -0
- package/src/presets/manifests/claude.yml +3 -0
- package/src/presets/manifests/gemini.yml +15 -0
- package/src/presets/models/agy.yml +24 -24
- package/src/presets/models/claude.yml +10 -10
- package/src/presets/models/codex.yml +10 -10
- package/src/presets/models/cursor.yml +10 -10
- package/src/presets/models/gemini.yml +10 -10
- package/src/presets/models/opencode.yml +10 -10
- package/presets/agy/scripts/post-tool.sh +0 -25
- package/presets/agy/scripts/pre-tool.sh +0 -56
|
@@ -1,592 +1,53 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
user-invokable: true
|
|
9
|
-
license: MIT
|
|
10
|
-
metadata:
|
|
11
|
-
author: lgzarturo
|
|
12
|
-
category: testing
|
|
13
|
-
|
|
14
|
-
compatibility:
|
|
15
|
-
tools: [claude, codex, gemini, agy, opencode]
|
|
16
|
-
stacks:
|
|
17
|
-
languages: []
|
|
18
|
-
frameworks: []
|
|
19
|
-
|
|
20
|
-
risk:
|
|
21
|
-
level: low
|
|
22
|
-
can_execute_shell: false
|
|
23
|
-
can_modify_files: true
|
|
24
|
-
requires_network: false
|
|
25
|
-
|
|
26
|
-
inputs: []
|
|
27
|
-
|
|
28
|
-
outputs: []
|
|
29
|
-
|
|
30
|
-
quality:
|
|
31
|
-
reviewed_by: codeconductor-core
|
|
32
|
-
version: 0.1.0
|
|
2
|
+
name: testing-tdd
|
|
3
|
+
description:
|
|
4
|
+
Guides agents through Red-Green-Refactor with runner-captured evidence.
|
|
5
|
+
Use when running /cc-tdd-cycle, writing tests before implementation, or
|
|
6
|
+
Global TDD required is yes.
|
|
33
7
|
---
|
|
34
8
|
|
|
35
|
-
|
|
36
|
-
|
|
37
9
|
# Test-Driven Development
|
|
38
10
|
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
```text
|
|
42
|
-
┌─────────────────────────────────────────────┐
|
|
43
|
-
│ │
|
|
44
|
-
│ RED → write a failing test │
|
|
45
|
-
│ ↓ │
|
|
46
|
-
│ GREEN → write the minimum code to pass │
|
|
47
|
-
│ ↓ │
|
|
48
|
-
│ REFACTOR → clean up without breaking │
|
|
49
|
-
│ ↓ │
|
|
50
|
-
│ repeat ───────────────────────────────────┘
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**Red**: Write a test that describes one behavior you want. Run it. It must
|
|
54
|
-
fail — if it passes without implementation, the test is not testing anything.
|
|
55
|
-
|
|
56
|
-
**Green**: Write the simplest code that makes the test pass. Do not optimize.
|
|
57
|
-
Do not add features. Just pass the test.
|
|
58
|
-
|
|
59
|
-
**Refactor**: Clean up duplication, naming, and structure. Run the tests after
|
|
60
|
-
every change. If any test breaks, the refactor changed behavior — that is a bug.
|
|
61
|
-
|
|
62
|
-
The cycle is short. Each iteration should take minutes, not hours. If a cycle
|
|
63
|
-
takes longer than 30 minutes, the behavior being tested is too large — split it.
|
|
64
|
-
|
|
65
|
-
## When to Apply TDD
|
|
66
|
-
|
|
67
|
-
**Apply TDD for:**
|
|
68
|
-
|
|
69
|
-
- New business logic with clear rules (validation, calculations, state machines)
|
|
70
|
-
- Bug fixes — write a regression test that reproduces the bug first
|
|
71
|
-
- Public service layer methods
|
|
72
|
-
- API endpoints with defined acceptance criteria
|
|
73
|
-
|
|
74
|
-
**Do not apply TDD for:**
|
|
75
|
-
|
|
76
|
-
- Exploratory code where the design is not yet known — spike first, then write
|
|
77
|
-
tests for the final design
|
|
78
|
-
- Trivial scaffolding (data class constructors, getters)
|
|
79
|
-
- Database migrations — test the resulting schema state, not the migration steps
|
|
80
|
-
- Third-party SDK wrappers where behavior is owned by the library
|
|
81
|
-
|
|
82
|
-
## Test Pyramid
|
|
83
|
-
|
|
84
|
-
```text
|
|
85
|
-
/\
|
|
86
|
-
/ \
|
|
87
|
-
/ E2E\ 10% — full browser/API flows, happy path + critical errors
|
|
88
|
-
/------\
|
|
89
|
-
/ Integ \ 20% — components with real dependencies (DB, HTTP clients)
|
|
90
|
-
/----------\
|
|
91
|
-
/ Unit \ 70% — isolated logic, mocked dependencies, sub-millisecond
|
|
92
|
-
/______________\
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Unit tests are the TDD workhorse. Write them first. Integration tests verify
|
|
96
|
-
that components connect correctly. E2E tests verify that the system works for
|
|
97
|
-
the user — keep them minimal and focused on the critical paths.
|
|
98
|
-
|
|
99
|
-
## TDD Rules
|
|
100
|
-
|
|
101
|
-
**One failing test at a time.** Write one test, make it pass, then write the
|
|
102
|
-
next. Do not write multiple failing tests before implementing.
|
|
103
|
-
|
|
104
|
-
**The test must fail for the right reason.** A `NullPointerException` on setup
|
|
105
|
-
is not a meaningful failure — that is a broken test. The failure must be the
|
|
106
|
-
assertion, not an error in the test itself.
|
|
107
|
-
|
|
108
|
-
**Minimum implementation.** In the Green phase, return a hardcoded value if
|
|
109
|
-
that makes the test pass. The next test will force you to generalize.
|
|
110
|
-
|
|
111
|
-
**Refactor only on green.** Never refactor when tests are failing. You lose the
|
|
112
|
-
safety net that tells you whether the refactor changed behavior.
|
|
113
|
-
|
|
114
|
-
**Tests are not optional after the fact.** Writing tests after implementation
|
|
115
|
-
is not TDD. It is documentation. It catches far fewer design problems.
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
## Spring Boot + Kotlin
|
|
120
|
-
|
|
121
|
-
### Naming Convention
|
|
122
|
-
|
|
123
|
-
```kotlin
|
|
124
|
-
@Test
|
|
125
|
-
fun `should [expected behavior] when [condition]`()
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
```kotlin
|
|
129
|
-
@Test
|
|
130
|
-
fun `should return user when found by id`() { ... }
|
|
131
|
-
|
|
132
|
-
@Test
|
|
133
|
-
fun `should throw NotFoundException when user does not exist`() { ... }
|
|
134
|
-
|
|
135
|
-
@Test
|
|
136
|
-
fun `should not create user when email already exists`() { ... }
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### TDD Cycle — Unit Test Example
|
|
140
|
-
|
|
141
|
-
**Requirement**: `UserService.create()` should reject duplicate emails.
|
|
142
|
-
|
|
143
|
-
**Red** — write the failing test first:
|
|
144
|
-
|
|
145
|
-
```kotlin
|
|
146
|
-
@ExtendWith(MockKExtension::class)
|
|
147
|
-
class UserServiceTest {
|
|
148
|
-
|
|
149
|
-
@MockK
|
|
150
|
-
private lateinit var userRepository: UserRepository
|
|
151
|
-
|
|
152
|
-
private lateinit var userService: UserService
|
|
153
|
-
|
|
154
|
-
@BeforeEach
|
|
155
|
-
fun setUp() {
|
|
156
|
-
userService = UserService(userRepository)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
@Test
|
|
160
|
-
fun `should throw ConflictException when email already exists`() {
|
|
161
|
-
// Arrange
|
|
162
|
-
val email = "existing@example.com"
|
|
163
|
-
every { userRepository.existsByEmail(email) } returns true
|
|
164
|
-
|
|
165
|
-
// Act & Assert
|
|
166
|
-
assertThrows<ConflictException> {
|
|
167
|
-
userService.create(email = email, name = "Test")
|
|
168
|
-
}
|
|
169
|
-
verify(exactly = 0) { userRepository.save(any()) }
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Run → fails (method does not exist yet).
|
|
175
|
-
|
|
176
|
-
**Green** — minimum implementation:
|
|
177
|
-
|
|
178
|
-
```kotlin
|
|
179
|
-
class UserService(private val userRepository: UserRepository) {
|
|
180
|
-
|
|
181
|
-
fun create(email: String, name: String): User {
|
|
182
|
-
if (userRepository.existsByEmail(email)) {
|
|
183
|
-
throw ConflictException("Email $email is already registered")
|
|
184
|
-
}
|
|
185
|
-
return userRepository.save(User(email = email, name = name))
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Run → passes.
|
|
191
|
-
|
|
192
|
-
**Refactor** — extract the check into a private guard, add KDoc only if the
|
|
193
|
-
domain rule is non-obvious. Run tests → still green.
|
|
194
|
-
|
|
195
|
-
**Next test** — happy path:
|
|
196
|
-
|
|
197
|
-
```kotlin
|
|
198
|
-
@Test
|
|
199
|
-
fun `should create and return user when email is unique`() {
|
|
200
|
-
val email = "new@example.com"
|
|
201
|
-
val saved = User(id = UUID.randomUUID(), email = email, name = "New User")
|
|
202
|
-
|
|
203
|
-
every { userRepository.existsByEmail(email) } returns false
|
|
204
|
-
every { userRepository.save(any()) } returns saved
|
|
205
|
-
|
|
206
|
-
val result = userService.create(email = email, name = "New User")
|
|
207
|
-
|
|
208
|
-
assertThat(result.email).isEqualTo(email)
|
|
209
|
-
verify(exactly = 1) { userRepository.save(any()) }
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### TDD Cycle — Integration Test Example
|
|
214
|
-
|
|
215
|
-
**Requirement**: `GET /api/users/{id}` returns 404 when the user does not exist.
|
|
216
|
-
|
|
217
|
-
**Red**:
|
|
218
|
-
|
|
219
|
-
```kotlin
|
|
220
|
-
@SpringBootTest
|
|
221
|
-
@AutoConfigureMockMvc
|
|
222
|
-
class UserControllerTest {
|
|
223
|
-
|
|
224
|
-
@Autowired
|
|
225
|
-
private lateinit var mockMvc: MockMvc
|
|
226
|
-
|
|
227
|
-
@Test
|
|
228
|
-
fun `should return 404 when user does not exist`() {
|
|
229
|
-
mockMvc.perform(get("/api/users/${UUID.randomUUID()}"))
|
|
230
|
-
.andExpect(status().isNotFound)
|
|
231
|
-
.andExpect(jsonPath("$.code").value("NOT_FOUND"))
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
Run → fails (endpoint may not exist yet, or returns wrong status).
|
|
237
|
-
|
|
238
|
-
**Green** — add/fix the endpoint and error handler. Run → passes.
|
|
239
|
-
|
|
240
|
-
**Refactor** — extract error response builder if duplicated across handlers.
|
|
241
|
-
|
|
242
|
-
### MockK Quick Reference
|
|
243
|
-
|
|
244
|
-
```kotlin
|
|
245
|
-
// Stub return value
|
|
246
|
-
every { repo.findById(id) } returns Optional.of(user)
|
|
247
|
-
|
|
248
|
-
// Stub exception
|
|
249
|
-
every { repo.save(any()) } throws DataIntegrityViolationException("duplicate")
|
|
250
|
-
|
|
251
|
-
// Verify call count
|
|
252
|
-
verify(exactly = 1) { repo.save(any()) }
|
|
253
|
-
verify(exactly = 0) { emailService.send(any()) }
|
|
254
|
-
|
|
255
|
-
// Capture argument
|
|
256
|
-
val slot = slot<User>()
|
|
257
|
-
every { repo.save(capture(slot)) } returns savedUser
|
|
258
|
-
assertThat(slot.captured.email).isEqualTo("expected@example.com")
|
|
259
|
-
|
|
260
|
-
// Coroutines
|
|
261
|
-
coEvery { repo.findById(id) } returns user
|
|
262
|
-
coVerify(exactly = 1) { repo.findById(id) }
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
---
|
|
11
|
+
## Overview
|
|
266
12
|
|
|
267
|
-
|
|
13
|
+
Red (failing test) → Green (minimal code) → Refactor. Evidence comes from
|
|
14
|
+
`captureTddSuiteEvidence`, not handmade JSON.
|
|
268
15
|
|
|
269
|
-
|
|
16
|
+
## When to Use
|
|
270
17
|
|
|
271
|
-
|
|
272
|
-
def test_[behavior]_when_[condition]():
|
|
273
|
-
```
|
|
18
|
+
- `/cc-tdd-cycle`, new behavior, bug fixes, TDD-required OpenSpec items
|
|
274
19
|
|
|
275
|
-
|
|
276
|
-
def test_returns_user_when_found_by_id(): ...
|
|
277
|
-
def test_raises_not_found_when_user_does_not_exist(): ...
|
|
278
|
-
def test_does_not_create_user_when_email_already_exists(): ...
|
|
279
|
-
```
|
|
20
|
+
**NOT** for docs-only changes or when the Task Card forbids tests.
|
|
280
21
|
|
|
281
|
-
|
|
22
|
+
## Process
|
|
282
23
|
|
|
283
|
-
|
|
24
|
+
1. Write the failing test that encodes one acceptance criterion. Run the suite.
|
|
25
|
+
It MUST fail (`suiteFails === true`).
|
|
26
|
+
2. Implement the minimum that turns it green. Do not expand scope.
|
|
27
|
+
3. Refactor only with a green suite.
|
|
28
|
+
4. Capture evidence via the verification runner (`openspec done` on test/implement
|
|
29
|
+
when TDD is required).
|
|
30
|
+
5. Cover happy path, edge, and error for each behavior.
|
|
284
31
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
```python
|
|
288
|
-
# tests/users/test_services.py
|
|
289
|
-
import pytest
|
|
290
|
-
from unittest.mock import MagicMock
|
|
291
|
-
from apps.users.services import UserService
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
def test_raises_when_email_already_exists():
|
|
295
|
-
repo = MagicMock()
|
|
296
|
-
repo.exists_by_email.return_value = True
|
|
297
|
-
service = UserService(repo)
|
|
298
|
-
|
|
299
|
-
with pytest.raises(ValueError, match="already registered"):
|
|
300
|
-
service.create(email="taken@example.com", name="Test")
|
|
301
|
-
|
|
302
|
-
repo.save.assert_not_called()
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
Run → fails (`UserService` does not exist).
|
|
306
|
-
|
|
307
|
-
**Green**:
|
|
308
|
-
|
|
309
|
-
```python
|
|
310
|
-
# apps/users/services.py
|
|
311
|
-
class UserService:
|
|
312
|
-
|
|
313
|
-
def __init__(self, repository):
|
|
314
|
-
self._repo = repository
|
|
315
|
-
|
|
316
|
-
def create(self, *, email: str, name: str):
|
|
317
|
-
if self._repo.exists_by_email(email):
|
|
318
|
-
raise ValueError(f"Email {email!r} is already registered")
|
|
319
|
-
return self._repo.save({"email": email, "name": name})
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
Run → passes.
|
|
323
|
-
|
|
324
|
-
### pytest with Django and factory-boy
|
|
325
|
-
|
|
326
|
-
```python
|
|
327
|
-
# tests/users/factories.py
|
|
328
|
-
import factory
|
|
329
|
-
from factory.django import DjangoModelFactory
|
|
330
|
-
from apps.users.models import User
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
class UserFactory(DjangoModelFactory):
|
|
334
|
-
class Meta:
|
|
335
|
-
model = User
|
|
336
|
-
|
|
337
|
-
email = factory.Sequence(lambda n: f"user{n}@example.com")
|
|
338
|
-
name = factory.Faker("name")
|
|
339
|
-
is_active = True
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
```python
|
|
343
|
-
# conftest.py
|
|
344
|
-
import pytest
|
|
345
|
-
from pytest_factoryboy import register
|
|
346
|
-
from tests.users.factories import UserFactory
|
|
347
|
-
|
|
348
|
-
register(UserFactory)
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
```python
|
|
352
|
-
# tests/users/test_services.py
|
|
353
|
-
import pytest
|
|
354
|
-
from apps.users.services import UserService
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
@pytest.mark.django_db
|
|
358
|
-
def test_create_user_succeeds_with_unique_email():
|
|
359
|
-
service = UserService()
|
|
360
|
-
|
|
361
|
-
user = service.create(email="new@example.com", name="Alice")
|
|
362
|
-
|
|
363
|
-
assert user.pk is not None
|
|
364
|
-
assert user.email == "new@example.com"
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
@pytest.mark.django_db
|
|
368
|
-
def test_create_user_raises_when_email_taken(user_factory):
|
|
369
|
-
user_factory(email="taken@example.com")
|
|
370
|
-
|
|
371
|
-
with pytest.raises(ValueError, match="already registered"):
|
|
372
|
-
UserService().create(email="taken@example.com", name="Bob")
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### pytest Fixtures
|
|
376
|
-
|
|
377
|
-
```python
|
|
378
|
-
# conftest.py
|
|
379
|
-
import pytest
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
@pytest.fixture
|
|
383
|
-
def authenticated_client(client, user_factory):
|
|
384
|
-
user = user_factory()
|
|
385
|
-
client.force_login(user)
|
|
386
|
-
return client, user
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
# In test
|
|
390
|
-
def test_profile_requires_auth(client):
|
|
391
|
-
response = client.get("/api/profile/")
|
|
392
|
-
assert response.status_code == 401
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
def test_profile_returns_user_data(authenticated_client):
|
|
396
|
-
client, user = authenticated_client
|
|
397
|
-
response = client.get("/api/profile/")
|
|
398
|
-
assert response.status_code == 200
|
|
399
|
-
assert response.json()["email"] == user.email
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
## Next.js / Astro (Vitest + RTL + Playwright)
|
|
405
|
-
|
|
406
|
-
### Naming Convention
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
it('should [behavior] when [condition]', () => { ... })
|
|
410
|
-
describe('ComponentName', () => {
|
|
411
|
-
describe('when [state]', () => {
|
|
412
|
-
it('should [behavior]', () => { ... })
|
|
413
|
-
})
|
|
414
|
-
})
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
### TDD Cycle — Component Test Example (Vitest + RTL)
|
|
418
|
-
|
|
419
|
-
**Requirement**: `<Counter>` increments when the button is clicked.
|
|
420
|
-
|
|
421
|
-
**Red**:
|
|
422
|
-
|
|
423
|
-
```typescript
|
|
424
|
-
// components/counter.test.tsx
|
|
425
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
426
|
-
import { Counter } from './counter';
|
|
427
|
-
|
|
428
|
-
describe('Counter', () => {
|
|
429
|
-
it('should display initial count of 0', () => {
|
|
430
|
-
render(<Counter />);
|
|
431
|
-
expect(screen.getByText('Count: 0')).toBeInTheDocument();
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
it('should increment count when button is clicked', () => {
|
|
435
|
-
render(<Counter />);
|
|
436
|
-
fireEvent.click(screen.getByRole('button', { name: /increment/i }));
|
|
437
|
-
expect(screen.getByText('Count: 1')).toBeInTheDocument();
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
Run → fails (component does not exist).
|
|
443
|
-
|
|
444
|
-
**Green**:
|
|
445
|
-
|
|
446
|
-
```tsx
|
|
447
|
-
// components/counter.tsx
|
|
448
|
-
'use client';
|
|
449
|
-
import { useState } from 'react';
|
|
450
|
-
|
|
451
|
-
export function Counter() {
|
|
452
|
-
const [count, setCount] = useState(0);
|
|
453
|
-
return (
|
|
454
|
-
<div>
|
|
455
|
-
<p>Count: {count}</p>
|
|
456
|
-
<button onClick={() => setCount(c => c + 1)} aria-label="increment">
|
|
457
|
-
+
|
|
458
|
-
</button>
|
|
459
|
-
</div>
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
Run → passes.
|
|
465
|
-
|
|
466
|
-
**Refactor** — extract `useCounter` hook if logic grows. Run tests → green.
|
|
467
|
-
|
|
468
|
-
### Vitest Setup
|
|
469
|
-
|
|
470
|
-
```typescript
|
|
471
|
-
// vitest.config.ts
|
|
472
|
-
import { defineConfig } from 'vitest/config';
|
|
473
|
-
import react from '@vitejs/plugin-react';
|
|
474
|
-
|
|
475
|
-
export default defineConfig({
|
|
476
|
-
plugins: [react()],
|
|
477
|
-
test: {
|
|
478
|
-
environment: 'jsdom',
|
|
479
|
-
globals: true,
|
|
480
|
-
setupFiles: ['./vitest.setup.ts'],
|
|
481
|
-
},
|
|
482
|
-
});
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
```typescript
|
|
486
|
-
// vitest.setup.ts
|
|
487
|
-
import '@testing-library/jest-dom';
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
### Server Action Testing
|
|
491
|
-
|
|
492
|
-
Test Server Actions by calling them directly in unit tests — no HTTP overhead.
|
|
493
|
-
|
|
494
|
-
```typescript
|
|
495
|
-
// app/posts/create/actions.test.ts
|
|
496
|
-
import { createPost } from './actions';
|
|
497
|
-
|
|
498
|
-
vi.mock('@/lib/db', () => ({
|
|
499
|
-
db: {
|
|
500
|
-
post: {
|
|
501
|
-
create: vi.fn(),
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
}));
|
|
505
|
-
|
|
506
|
-
describe('createPost', () => {
|
|
507
|
-
it('should return validation error when title is empty', async () => {
|
|
508
|
-
const formData = new FormData();
|
|
509
|
-
formData.set('title', '');
|
|
510
|
-
formData.set('content', 'Some content');
|
|
511
|
-
|
|
512
|
-
const result = await createPost(formData);
|
|
513
|
-
|
|
514
|
-
expect(result?.error?.title).toBeDefined();
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
### E2E Testing with Playwright
|
|
520
|
-
|
|
521
|
-
Reserve Playwright for critical user flows only. Do not replicate unit test
|
|
522
|
-
scenarios in E2E.
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
// e2e/auth.spec.ts
|
|
526
|
-
import { test, expect } from '@playwright/test';
|
|
527
|
-
|
|
528
|
-
test.describe('Authentication', () => {
|
|
529
|
-
test('should redirect to login when accessing protected route unauthenticated', async ({ page }) => {
|
|
530
|
-
await page.goto('/dashboard');
|
|
531
|
-
await expect(page).toHaveURL('/login');
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
test('should show dashboard after successful login', async ({ page }) => {
|
|
535
|
-
await page.goto('/login');
|
|
536
|
-
await page.getByLabel('Email').fill('user@example.com');
|
|
537
|
-
await page.getByLabel('Password').fill('password123');
|
|
538
|
-
await page.getByRole('button', { name: 'Sign in' }).click();
|
|
539
|
-
await expect(page).toHaveURL('/dashboard');
|
|
540
|
-
});
|
|
541
|
-
});
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
```typescript
|
|
545
|
-
// playwright.config.ts
|
|
546
|
-
import { defineConfig } from '@playwright/test';
|
|
547
|
-
|
|
548
|
-
export default defineConfig({
|
|
549
|
-
testDir: './e2e',
|
|
550
|
-
use: {
|
|
551
|
-
baseURL: 'http://localhost:3000',
|
|
552
|
-
},
|
|
553
|
-
webServer: {
|
|
554
|
-
command: 'npm run build && npm run start',
|
|
555
|
-
port: 3000,
|
|
556
|
-
reuseExistingServer: !process.env.CI,
|
|
557
|
-
},
|
|
558
|
-
});
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
---
|
|
32
|
+
Local: `bun run dev`. Pyramid default: many unit, fewer integration, rare E2E.
|
|
562
33
|
|
|
563
|
-
##
|
|
34
|
+
## Common Rationalizations
|
|
564
35
|
|
|
565
|
-
|
|
36
|
+
| Rationalization | Reality |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| I'll add tests later | Later means never. Red first. |
|
|
39
|
+
| This is too small to test | If it can break, it needs a failing test first. |
|
|
40
|
+
| I'll write the evidence JSON | Handmade TDD JSON is rejected. |
|
|
566
41
|
|
|
567
|
-
|
|
568
|
-
1. Unit tests — sub-second feedback; block all subsequent steps on failure
|
|
569
|
-
2. Integration tests — real DB, real HTTP; slower but necessary
|
|
570
|
-
3. E2E tests — slowest; run only on main branch or release branches
|
|
571
|
-
```
|
|
42
|
+
## Red Flags
|
|
572
43
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
test:
|
|
577
|
-
steps:
|
|
578
|
-
- name: Unit tests
|
|
579
|
-
run: npx vitest run --reporter=verbose
|
|
580
|
-
- name: Integration tests
|
|
581
|
-
run: npx vitest run --project=integration
|
|
582
|
-
- name: E2E tests
|
|
583
|
-
if: github.ref == 'refs/heads/main'
|
|
584
|
-
run: npx playwright test
|
|
585
|
-
```
|
|
44
|
+
- Tests that assert implementation details instead of behavior
|
|
45
|
+
- Green without a recorded red
|
|
46
|
+
- Skipping error cases
|
|
586
47
|
|
|
587
|
-
|
|
48
|
+
## Verification
|
|
588
49
|
|
|
589
|
-
-
|
|
590
|
-
-
|
|
591
|
-
-
|
|
592
|
-
|
|
50
|
+
- [ ] Suite failed before implement
|
|
51
|
+
- [ ] Suite passed after implement
|
|
52
|
+
- [ ] Runner evidence exists (not handmade)
|
|
53
|
+
- [ ] Optional: `bun run dev scorecard suite-run --suite workflow-gates`
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: using-cc-skills
|
|
3
|
+
description:
|
|
4
|
+
Maps incoming work to the CodeConductor slash command and workflow skill.
|
|
5
|
+
Use when starting a session or deciding which /cc-* command applies.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Using CodeConductor skills
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Pick one slash command. Follow its skill. Invoke CLI for gates. Do not invent
|
|
13
|
+
a parallel process.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- Start of a session, ambiguous request, or "which /cc should I run?"
|
|
18
|
+
|
|
19
|
+
## Process
|
|
20
|
+
|
|
21
|
+
| Intent | Command | Skill |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| New backlog item | `/cc-backlog` | `backlog` |
|
|
24
|
+
| Deliver a BC-xxx item | `/cc-openspec` | `openspec` |
|
|
25
|
+
| New feature | `/cc-feature` | `openspec` + `testing-tdd` |
|
|
26
|
+
| Bug fix | `/cc-fix` | `testing-tdd` |
|
|
27
|
+
| Review a diff | `/cc-review` | `evaluation` |
|
|
28
|
+
| TDD cycle only | `/cc-tdd-cycle` | `testing-tdd` |
|
|
29
|
+
| Scorecard / suites | `/cc-scorecard` | `evaluation` |
|
|
30
|
+
|
|
31
|
+
Then run the matching CLI (`openspec validate`, `scorecard create --from-diff`,
|
|
32
|
+
`hook pre-tool`, `scorecard suite-run`).
|
|
33
|
+
|
|
34
|
+
## Common Rationalizations
|
|
35
|
+
|
|
36
|
+
| Rationalization | Reality |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| I'll skip the slash and just code | Skipping the workflow is a defect. |
|
|
39
|
+
|
|
40
|
+
## Red Flags
|
|
41
|
+
|
|
42
|
+
- Two slash commands in parallel that mutate the same files
|
|
43
|
+
- Implementing before `openspec analyze` when a change folder is active
|
|
44
|
+
|
|
45
|
+
## Verification
|
|
46
|
+
|
|
47
|
+
- [ ] One command selected and shown to the user
|
|
48
|
+
- [ ] Matching skill loaded before edits
|
|
Binary file
|