Skip to content

Commit a768267

Browse files
abaddonclaude
andauthored
Claude/burraco feature workflow zo1 w8 (#76)
* docs: Add comprehensive feature development workflow for distributed architecture This workflow document defines a structured approach for adding new features to the Burraco-Vertx distributed system, including: - Six specialized phases: Discovery, Design, Domain Modeling, Implementation, Testing, and Integration - Agent architecture with specific skills per phase - LSP (Language Server Protocol) integration for all agents - BDD testing patterns with Cucumber/Gherkin - Pattern application: CQRS, Event Sourcing, Hexagonal Architecture, DDD - Complete checklists and code templates - Practical example: Adding "Place Meld" feature * feat: Add executable Claude Code workflow skills for feature development Implements a working Claude Code workflow with 7 executable skills: Skills Added: - /feature-workflow: Main orchestrator for the full development workflow - /feature-discovery: Phase 1 - Analyze requirements and assess impact - /feature-design: Phase 2 - Design events, commands, service interactions - /domain-modeling: Phase 3 - Create domain events, value objects, aggregates - /kotlin-implement: Phase 4 - Implement commands, handlers, Kafka integration - /bdd-test: Phase 5 - Create BDD/Cucumber tests and unit tests - /integration-docs: Phase 6 - Update documentation and verify integration Each skill provides: - Detailed step-by-step instructions - Code templates following project patterns - Verification checklists - References to existing codebase examples Configuration: - .claude/settings.json: Skill definitions and hooks - .claude/skills/*.md: Individual skill instructions Updated FEATURE_WORKFLOW.md with Quick Start guide showing how to use skills. * fix: Remove invalid hooks configuration from Claude Code settings The 'pre-commit' key is not a valid Claude Code hook event type. Claude Code hooks are for tool interception (PreToolUse, PostToolUse, etc.), not git operations. Git pre-commit hooks should go in .git/hooks/ if needed. The skills configuration remains intact and fully functional. * feat: Restructure Claude Code skills to proper SKILL.md format Fixes skill discovery by converting from flat .md files to the correct directory structure with SKILL.md files containing YAML frontmatter. Changes: - Convert each skill to a directory with SKILL.md inside - Add required YAML frontmatter (name, description) to each skill - Remove settings.json (skills are auto-discovered) - Update FEATURE_WORKFLOW.md with correct paths Skill structure now follows official Claude Code format: .claude/skills/[skill-name]/SKILL.md Available skills: - /feature-workflow - Main orchestrator - /feature-discovery - Phase 1: Analysis - /feature-design - Phase 2: Design - /domain-modeling - Phase 3: Domain model - /kotlin-implement - Phase 4: Implementation - /bdd-test - Phase 5: Testing - /integration-docs - Phase 6: Documentation * update gitignore --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent cd754ae commit a768267

9 files changed

Lines changed: 2610 additions & 1 deletion

File tree

.claude/skills/bdd-test/SKILL.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
---
2+
name: bdd-test
3+
description: Phase 5 of feature development - Create BDD tests with Cucumber/Gherkin and unit tests for the Burraco system. Use after kotlin-implement to add comprehensive test coverage.
4+
---
5+
6+
# BDD Testing Skill
7+
8+
This skill creates BDD acceptance tests and unit tests for the feature.
9+
10+
## Usage
11+
12+
```
13+
/bdd-test <feature-name>
14+
```
15+
16+
## Prerequisites
17+
18+
Run `/kotlin-implement` first to have the implementation ready.
19+
20+
## Instructions
21+
22+
### Step 1: Create Gherkin Feature File
23+
24+
Location: `e2eTest/src/test/resources/features/`
25+
26+
```gherkin
27+
# File: [feature-name].feature
28+
29+
@[feature-tag]
30+
Feature: [Feature Name]
31+
As a Burraco player
32+
I want to [action]
33+
So that [benefit]
34+
35+
Background:
36+
Given a game with 2 players is created
37+
And the game has been started with cards dealt
38+
39+
Scenario: Successfully [action]
40+
Given [precondition]
41+
When [action is performed]
42+
Then [expected result]
43+
And [additional verification]
44+
45+
Scenario: Reject [action] when [invalid condition]
46+
Given [invalid precondition]
47+
When [action is attempted]
48+
Then the action should be rejected with error "[error message]"
49+
50+
Scenario Outline: [Action] with various inputs
51+
Given [setup with <param>]
52+
When [action with <input>]
53+
Then the result should be <expected>
54+
55+
Examples:
56+
| param | input | expected |
57+
| val1 | in1 | result1 |
58+
| val2 | in2 | result2 |
59+
```
60+
61+
### Step 2: Create Step Definitions
62+
63+
Location: `e2eTest/src/test/kotlin/com/abaddon83/burraco/e2e/steps/`
64+
65+
```kotlin
66+
package com.abaddon83.burraco.e2e.steps
67+
68+
import com.abaddon83.burraco.e2e.support.TestContext
69+
import io.cucumber.java.en.Given
70+
import io.cucumber.java.en.When
71+
import io.cucumber.java.en.Then
72+
import org.assertj.core.api.Assertions.assertThat
73+
import org.awaitility.Awaitility.await
74+
import java.time.Duration
75+
76+
class [Feature]StepDefinitions(private val context: TestContext) {
77+
78+
private val httpClient get() = context.httpClient
79+
80+
@Given("the current player has {string}")
81+
fun givenPlayerHas(condition: String) {
82+
val playerView = httpClient.getPlayerView(
83+
context.currentPlayerId!!,
84+
context.gameId!!
85+
)
86+
assertThat(playerView.cards).isNotEmpty()
87+
}
88+
89+
@When("the player performs [feature] action")
90+
fun whenPlayerPerformsAction() {
91+
val request = JsonObject()
92+
.put("playerId", context.currentPlayerId?.toString())
93+
.put("param1", "value1")
94+
95+
context.lastResponse = httpClient.post(
96+
"/games/${context.gameId}/[feature]",
97+
request
98+
)
99+
}
100+
101+
@Then("the action should succeed")
102+
fun thenActionSucceeds() {
103+
assertThat(context.lastResponse?.statusCode())
104+
.isIn(200, 201, 204)
105+
}
106+
107+
@Then("the action should be rejected with error {string}")
108+
fun thenActionRejectedWithError(errorMessage: String) {
109+
assertThat(context.lastResponse?.statusCode())
110+
.isIn(400, 409, 422)
111+
val body = context.lastResponse?.bodyAsJsonObject()
112+
assertThat(body?.getString("error")).contains(errorMessage)
113+
}
114+
115+
@Then("the result should be visible in player view")
116+
fun thenResultVisibleInPlayerView() {
117+
await()
118+
.atMost(Duration.ofSeconds(10))
119+
.untilAsserted {
120+
val playerView = httpClient.getPlayerView(
121+
context.currentPlayerId!!,
122+
context.gameId!!
123+
)
124+
assertThat(playerView.[featureField]).isNotNull()
125+
}
126+
}
127+
}
128+
```
129+
130+
### Step 3: Create Unit Tests
131+
132+
Location: `[Service]/src/test/kotlin/com/abaddon83/burraco/[service]/commands/`
133+
134+
```kotlin
135+
// Happy path test
136+
internal class Given_[ValidState]_When_[Command]_Then_[Event] :
137+
KcqrsAggregateTestSpecification<[Aggregate]>() {
138+
139+
private val aggregateId = [Context]Identity.create()
140+
141+
override fun emptyAggregate() = { [InitialState].empty() }
142+
143+
override fun given(): List<IDomainEvent> = listOf(
144+
[PreviousEvent1].create(aggregateId, ...),
145+
[PreviousEvent2].create(aggregateId, ...)
146+
)
147+
148+
override fun `when`(): ICommand<[Aggregate]> = [CommandName](
149+
aggregateID = aggregateId,
150+
param1 = "validValue"
151+
)
152+
153+
override fun expected(): List<IDomainEvent> = listOf(
154+
[EventName].create(aggregateId, expectedResult)
155+
)
156+
157+
override fun expectedException(): Class<out Exception>? = null
158+
}
159+
160+
// Invalid state test
161+
internal class Given_[InvalidState]_When_[Command]_Then_Exception :
162+
KcqrsAggregateTestSpecification<[Aggregate]>() {
163+
// ... setup for invalid state ...
164+
override fun expected() = emptyList<IDomainEvent>()
165+
override fun expectedException() = UnsupportedOperationException::class.java
166+
}
167+
```
168+
169+
### Step 4: Run Tests
170+
171+
```bash
172+
# Run unit tests
173+
./gradlew :[Service]:test --tests "*[CommandName]*"
174+
175+
# Run E2E tests with fresh Docker images
176+
./gradlew :e2eTest:e2eTestClean
177+
178+
# Run specific feature
179+
./gradlew :e2eTest:test -Dcucumber.filter.tags="@[feature-tag]"
180+
```
181+
182+
### Step 5: Testing Checklist
183+
184+
```markdown
185+
### Gherkin Feature File
186+
- [ ] Feature file created
187+
- [ ] Happy path scenario
188+
- [ ] Error scenarios
189+
- [ ] Edge cases covered
190+
191+
### Step Definitions
192+
- [ ] All Given/When/Then steps implemented
193+
- [ ] Awaitility for async assertions
194+
- [ ] TestContext for shared state
195+
196+
### Unit Tests
197+
- [ ] Happy path test
198+
- [ ] Invalid state test
199+
- [ ] Validation test
200+
201+
### Execution
202+
- [ ] ./gradlew :[Service]:test passes
203+
- [ ] ./gradlew :e2eTest:e2eTestClean passes
204+
```
205+
206+
## Reference Files
207+
208+
- Feature File: `e2eTest/src/test/resources/features/game-creation.feature`
209+
- Step Definitions: `e2eTest/src/test/kotlin/com/abaddon83/burraco/e2e/steps/GameStepDefinitions.kt`
210+
- Unit Test: `Game/src/test/kotlin/com/abaddon83/burraco/game/commands/gameDraft/CreateGameTest.kt`
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
---
2+
name: domain-modeling
3+
description: Phase 3 of feature development - Create domain events, external events, value objects, and aggregate changes for the Burraco system. Use after feature-design to implement the domain model.
4+
---
5+
6+
# Domain Modeling Skill
7+
8+
This skill creates the domain model components including events, value objects, and aggregate modifications.
9+
10+
## Usage
11+
12+
```
13+
/domain-modeling <feature-name>
14+
```
15+
16+
## Prerequisites
17+
18+
Run `/feature-design` first to have the design specifications.
19+
20+
## Instructions
21+
22+
### Step 1: Create Domain Events
23+
24+
Location: `Common/src/main/kotlin/com/abaddon83/burraco/common/models/event/[context]/`
25+
26+
```kotlin
27+
// File: [EventName].kt
28+
package com.abaddon83.burraco.common.models.event.[context]
29+
30+
import com.abaddon83.burraco.common.models.[Context]Identity
31+
import com.abaddon83.burraco.common.models.event.EventHeader
32+
import java.util.UUID
33+
34+
data class [EventName](
35+
override val messageId: UUID,
36+
override val header: EventHeader,
37+
override val aggregateId: [Context]Identity,
38+
val specificField: SpecificType
39+
) : [Context]Event() {
40+
41+
companion object Factory {
42+
fun create(
43+
aggregateId: [Context]Identity,
44+
specificField: SpecificType
45+
): [EventName] = [EventName](
46+
messageId = UUID.randomUUID(),
47+
header = EventHeader.create("[context]"),
48+
aggregateId = aggregateId,
49+
specificField = specificField
50+
)
51+
}
52+
}
53+
```
54+
55+
### Step 2: Create External Events
56+
57+
Location: `Common/src/main/kotlin/com/abaddon83/burraco/common/externalEvents/[context]/`
58+
59+
```kotlin
60+
// File: [EventName]ExternalEvent.kt
61+
package com.abaddon83.burraco.common.externalEvents.[context]
62+
63+
data class [EventName]ExternalEvent(
64+
override val aggregateId: [Context]Identity,
65+
override val messageId: UUID,
66+
val specificField: String // Use primitive types for serialization
67+
) : [Context]ExternalEvent() {
68+
69+
companion object {
70+
fun from(event: [EventName]): [EventName]ExternalEvent =
71+
[EventName]ExternalEvent(
72+
aggregateId = event.aggregateId,
73+
messageId = event.messageId,
74+
specificField = event.specificField.toString()
75+
)
76+
}
77+
}
78+
```
79+
80+
### Step 3: Create Value Objects (if needed)
81+
82+
Location: `Common/src/main/kotlin/com/abaddon83/burraco/common/models/`
83+
84+
```kotlin
85+
data class [ValueObject] private constructor(
86+
val property1: Type1,
87+
val property2: Type2
88+
) {
89+
init {
90+
require(property1.isNotBlank()) { "property1 cannot be blank" }
91+
}
92+
93+
companion object {
94+
fun create(property1: Type1, property2: Type2): [ValueObject] =
95+
[ValueObject](property1, property2)
96+
}
97+
}
98+
```
99+
100+
### Step 4: Update Aggregate State Classes
101+
102+
Find the aggregate state class and add methods:
103+
104+
```kotlin
105+
// In existing state class (e.g., GameExecution.kt)
106+
107+
fun [actionName](param1: Type1, param2: Type2): [ResultState] {
108+
// 1. Validate preconditions
109+
require(someCondition) { "Precondition not met" }
110+
111+
// 2. Create and raise event
112+
val event = [EventName].create(aggregateId, computeValue(param1, param2))
113+
114+
// 3. Return new state with event applied
115+
return this.raiseEvent(event) as [ResultState]
116+
}
117+
118+
// Add event application
119+
override fun apply(event: IDomainEvent): [State] = when (event) {
120+
is [EventName] -> this.apply(event)
121+
else -> throw UnsupportedOperationException("Event not supported")
122+
}
123+
124+
private fun apply(event: [EventName]): [ResultState] {
125+
return [ResultState](
126+
aggregateId = this.aggregateId,
127+
newField = event.specificField
128+
)
129+
}
130+
```
131+
132+
### Step 5: Verification Checklist
133+
134+
```markdown
135+
## Domain Model Verification
136+
137+
### Events Created
138+
- [ ] Domain event in `Common/models/event/[context]/`
139+
- [ ] External event in `Common/externalEvents/[context]/`
140+
- [ ] Factory methods with `create()`
141+
142+
### Value Objects Created (if applicable)
143+
- [ ] Immutable data class
144+
- [ ] Private constructor with factory methods
145+
- [ ] Invariant validation in init block
146+
147+
### Aggregate Updates
148+
- [ ] Action method added to correct state class
149+
- [ ] Event creation in action method
150+
- [ ] Event application method added
151+
```
152+
153+
## Reference Files
154+
155+
- Event Example: `Common/src/main/kotlin/com/abaddon83/burraco/common/models/event/game/GameCreated.kt`
156+
- External Event Example: `Common/src/main/kotlin/com/abaddon83/burraco/common/externalEvents/game/GameCreatedExternalEvent.kt`
157+
- Value Object Example: `Common/src/main/kotlin/com/abaddon83/burraco/common/models/card/Card.kt`
158+
- State Class Example: `Game/src/main/kotlin/com/abaddon83/burraco/game/models/game/GameExecution.kt`

0 commit comments

Comments
 (0)