cc-codeconductor 1.0.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 +326 -87
- package/dist/core/compilation/compile-checker.d.ts +2 -0
- package/dist/core/loop/loop-engine.d.ts +3 -0
- package/dist/core/verification/verification-runner.d.ts +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3825 -597
- package/dist/library.js +195 -18
- package/dist/validation/schemas.d.ts +461 -44
- package/package.json +4 -1
- package/presets/agy/AGENTS.md +13 -9
- package/presets/agy/gates/pre-commit/GATE.md +5 -4
- package/presets/agy/hooks.json +2 -2
- package/presets/agy/scripts/invoke-hook.cjs +115 -0
- package/presets/agy/skills/backlog/SKILL.md +61 -0
- 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 +50 -16
- package/presets/agy/skills/security-ai-llm/SKILL.md +43 -0
- package/presets/agy/skills/security-blue-team/SKILL.md +43 -0
- package/presets/agy/skills/security-cloud/SKILL.md +43 -0
- package/presets/agy/skills/security-crypto/SKILL.md +43 -0
- package/presets/agy/skills/security-exploit-dev/SKILL.md +45 -0
- package/presets/agy/skills/security-grc/SKILL.md +43 -0
- package/presets/agy/skills/security-incident-response/SKILL.md +45 -0
- package/presets/agy/skills/security-log-analysis/SKILL.md +43 -0
- package/presets/agy/skills/security-malware-analysis/SKILL.md +44 -0
- package/presets/agy/skills/security-mobile/SKILL.md +43 -0
- package/presets/agy/skills/security-network/SKILL.md +43 -0
- package/presets/agy/skills/security-ot-ics/SKILL.md +43 -0
- package/presets/agy/skills/security-recon/SKILL.md +45 -0
- package/presets/agy/skills/security-red-team/SKILL.md +44 -0
- package/presets/agy/skills/security-reverse-engineering/SKILL.md +44 -0
- package/presets/agy/skills/security-soc-automation/SKILL.md +43 -0
- package/presets/agy/skills/security-threat-hunting/SKILL.md +43 -0
- package/presets/agy/skills/security-vuln-assessment/SKILL.md +45 -0
- package/presets/agy/skills/security-web/SKILL.md +44 -0
- 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-ask.md +2 -1
- package/presets/agy/workflows/cc-backlog.md +69 -0
- package/presets/agy/workflows/cc-db-migration.md +14 -0
- package/presets/agy/workflows/cc-explore.md +1 -1
- 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 +17 -1
- package/presets/agy/workflows/cc-scorecard.md +2 -0
- package/presets/agy/workflows/cc-security.md +180 -0
- package/presets/agy/workflows/cc-spec-mutation.md +191 -0
- package/presets/agy/workflows/cc-tdd-cycle.md +14 -0
- package/presets/claude/CLAUDE.md +4 -0
- package/presets/claude/commands/cc/api-contract.md +14 -0
- package/presets/claude/commands/cc/ask.md +2 -1
- package/presets/claude/commands/cc/backlog.md +104 -0
- package/presets/claude/commands/cc/db-migration.md +14 -0
- package/presets/claude/commands/cc/explore.md +1 -1
- 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 +30 -1
- package/presets/claude/commands/cc/review.md +3 -0
- package/presets/claude/commands/cc/scorecard.md +2 -0
- package/presets/claude/commands/cc/security.md +179 -0
- package/presets/claude/commands/cc/spec-mutation.md +190 -0
- package/presets/claude/commands/cc/tdd-cycle.md +17 -0
- package/presets/claude/gates/pre-commit/GATE.md +5 -4
- package/presets/claude/settings.json +13 -11
- package/presets/claude/skills/backlog/SKILL.md +61 -0
- package/presets/claude/skills/evaluation/SKILL.md +47 -24
- package/presets/claude/skills/openspec/SKILL.md +46 -34
- package/presets/claude/skills/security/SKILL.md +382 -0
- package/presets/claude/skills/security-ai-llm/SKILL.md +43 -0
- package/presets/claude/skills/security-blue-team/SKILL.md +43 -0
- package/presets/claude/skills/security-cloud/SKILL.md +43 -0
- package/presets/claude/skills/security-crypto/SKILL.md +43 -0
- package/presets/claude/skills/security-exploit-dev/SKILL.md +45 -0
- package/presets/claude/skills/security-grc/SKILL.md +43 -0
- package/presets/claude/skills/security-incident-response/SKILL.md +45 -0
- package/presets/claude/skills/security-log-analysis/SKILL.md +43 -0
- package/presets/claude/skills/security-malware-analysis/SKILL.md +44 -0
- package/presets/claude/skills/security-mobile/SKILL.md +43 -0
- package/presets/claude/skills/security-network/SKILL.md +43 -0
- package/presets/claude/skills/security-ot-ics/SKILL.md +43 -0
- package/presets/claude/skills/security-recon/SKILL.md +45 -0
- package/presets/claude/skills/security-red-team/SKILL.md +44 -0
- package/presets/claude/skills/security-reverse-engineering/SKILL.md +44 -0
- package/presets/claude/skills/security-soc-automation/SKILL.md +43 -0
- package/presets/claude/skills/security-threat-hunting/SKILL.md +43 -0
- package/presets/claude/skills/security-vuln-assessment/SKILL.md +45 -0
- package/presets/claude/skills/security-web/SKILL.md +44 -0
- 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/commands/cc-ask.md +2 -1
- package/presets/codex/gates/pre-commit/GATE.md +5 -4
- 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/AGENTS.md +2 -2
- package/presets/cursor/commands/cc/api-contract.md +14 -0
- package/presets/cursor/commands/cc/ask.md +2 -1
- package/presets/cursor/commands/cc/backlog.md +105 -0
- package/presets/cursor/commands/cc/db-migration.md +14 -0
- package/presets/cursor/commands/cc/explore.md +1 -1
- 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 +30 -1
- package/presets/cursor/commands/cc/scorecard.md +2 -0
- package/presets/cursor/commands/cc/security.md +179 -0
- package/presets/cursor/commands/cc/spec-mutation.md +190 -0
- package/presets/cursor/commands/cc/tdd-cycle.md +14 -0
- package/presets/cursor/gates/pre-commit/GATE.md +5 -4
- package/presets/cursor/skills/backlog/SKILL.md +61 -0
- package/presets/cursor/skills/evaluation/SKILL.md +61 -4
- package/presets/cursor/skills/openspec/SKILL.md +47 -33
- package/presets/cursor/skills/security-ai-llm/SKILL.md +43 -0
- package/presets/cursor/skills/security-blue-team/SKILL.md +43 -0
- package/presets/cursor/skills/security-cloud/SKILL.md +43 -0
- package/presets/cursor/skills/security-crypto/SKILL.md +43 -0
- package/presets/cursor/skills/security-exploit-dev/SKILL.md +45 -0
- package/presets/cursor/skills/security-grc/SKILL.md +43 -0
- package/presets/cursor/skills/security-incident-response/SKILL.md +45 -0
- package/presets/cursor/skills/security-log-analysis/SKILL.md +43 -0
- package/presets/cursor/skills/security-malware-analysis/SKILL.md +44 -0
- package/presets/cursor/skills/security-mobile/SKILL.md +43 -0
- package/presets/cursor/skills/security-network/SKILL.md +43 -0
- package/presets/cursor/skills/security-ot-ics/SKILL.md +43 -0
- package/presets/cursor/skills/security-recon/SKILL.md +45 -0
- package/presets/cursor/skills/security-red-team/SKILL.md +44 -0
- package/presets/cursor/skills/security-reverse-engineering/SKILL.md +44 -0
- package/presets/cursor/skills/security-soc-automation/SKILL.md +43 -0
- package/presets/cursor/skills/security-threat-hunting/SKILL.md +43 -0
- package/presets/cursor/skills/security-vuln-assessment/SKILL.md +45 -0
- package/presets/cursor/skills/security-web/SKILL.md +44 -0
- 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-ask.md +2 -1
- package/presets/opencode/commands/cc-backlog.md +68 -0
- package/presets/opencode/commands/cc-db-migration.md +14 -0
- package/presets/opencode/commands/cc-explore.md +1 -1
- 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 +17 -1
- package/presets/opencode/commands/cc-scorecard.md +2 -0
- package/presets/opencode/commands/cc-security.md +179 -0
- package/presets/opencode/commands/cc-spec-mutation.md +190 -0
- package/presets/opencode/commands/cc-tdd-cycle.md +14 -0
- package/presets/opencode/gates/pre-commit/GATE.md +5 -4
- 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 +61 -0
- package/presets/opencode/skills/evaluation/SKILL.md +61 -2
- package/presets/opencode/skills/openspec/SKILL.md +47 -31
- package/presets/opencode/skills/security-ai-llm/SKILL.md +43 -0
- package/presets/opencode/skills/security-blue-team/SKILL.md +43 -0
- package/presets/opencode/skills/security-cloud/SKILL.md +43 -0
- package/presets/opencode/skills/security-crypto/SKILL.md +43 -0
- package/presets/opencode/skills/security-exploit-dev/SKILL.md +45 -0
- package/presets/opencode/skills/security-grc/SKILL.md +43 -0
- package/presets/opencode/skills/security-incident-response/SKILL.md +45 -0
- package/presets/opencode/skills/security-log-analysis/SKILL.md +43 -0
- package/presets/opencode/skills/security-malware-analysis/SKILL.md +44 -0
- package/presets/opencode/skills/security-mobile/SKILL.md +43 -0
- package/presets/opencode/skills/security-network/SKILL.md +43 -0
- package/presets/opencode/skills/security-ot-ics/SKILL.md +43 -0
- package/presets/opencode/skills/security-recon/SKILL.md +45 -0
- package/presets/opencode/skills/security-red-team/SKILL.md +44 -0
- package/presets/opencode/skills/security-reverse-engineering/SKILL.md +44 -0
- package/presets/opencode/skills/security-soc-automation/SKILL.md +43 -0
- package/presets/opencode/skills/security-threat-hunting/SKILL.md +43 -0
- package/presets/opencode/skills/security-vuln-assessment/SKILL.md +45 -0
- package/presets/opencode/skills/security-web/SKILL.md +44 -0
- 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/council/council.yml +12 -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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-web
|
|
3
|
+
description: >
|
|
4
|
+
Review and harden web apps (authz, XSS, CSRF, SSRF, injection) on code you maintain. Complements the OWASP `security` skill. No exploit kits.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Web Application Security
|
|
8
|
+
|
|
9
|
+
## Authorization
|
|
10
|
+
|
|
11
|
+
Use this skill only with **explicit written authorization** for a named
|
|
12
|
+
scope (systems, environments, and time window). If authorization is missing,
|
|
13
|
+
expired, or the request is for someone else's systems, **stop**.
|
|
14
|
+
|
|
15
|
+
This skill is **defensive and owner-authorized**. It does not authorize
|
|
16
|
+
offensive cyber operations.
|
|
17
|
+
|
|
18
|
+
## Do
|
|
19
|
+
|
|
20
|
+
- Enforce authorization on every handler; never rely on the UI
|
|
21
|
+
- Parameterize queries; reject unknown fields
|
|
22
|
+
- Treat cookies, tokens, and redirects as untrusted until validated
|
|
23
|
+
- Add tests for authz, CSRF, and open redirects when you change those paths
|
|
24
|
+
|
|
25
|
+
## Do not
|
|
26
|
+
|
|
27
|
+
- Provide XSS or SQLi payloads for use against systems you do not own
|
|
28
|
+
- Disable CSRF or SameSite 'to make local dev easier' in production configs
|
|
29
|
+
|
|
30
|
+
- Do not produce exploit payloads, malware, or attack procedures.
|
|
31
|
+
|
|
32
|
+
## How to Use
|
|
33
|
+
|
|
34
|
+
Load this skill from `/cc-security` when the Task Card domain is `security-web`.
|
|
35
|
+
Example prompts:
|
|
36
|
+
|
|
37
|
+
- `Review our Next.js Server Actions for IDOR and CSRF. Propose tests, not exploits.`
|
|
38
|
+
- `Harden the file-upload endpoint: type, size, path containment, and tests.`
|
|
39
|
+
|
|
40
|
+
## Integration
|
|
41
|
+
|
|
42
|
+
- Workflow: `/cc-security` (CCEP command `security`)
|
|
43
|
+
- Existing OWASP application-security skill remains `security` (not this id)
|
|
44
|
+
- High-risk changes still require `security-reviewer`
|
|
@@ -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`
|