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