@eventmodelers/cli 0.0.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 (220) hide show
  1. package/README.md +175 -0
  2. package/cli.js +676 -0
  3. package/package.json +35 -0
  4. package/shared/build-kit/code-export.mjs +560 -0
  5. package/shared/build-kit/lib/ollama-agent.js +147 -0
  6. package/shared/build-kit/package.json +11 -0
  7. package/shared/build-kit/ralph-ollama.js +39 -0
  8. package/shared/build-kit/realtime-agent.js +18 -0
  9. package/stacks/axon/templates/.claude/skills/build-automation/SKILL.md +407 -0
  10. package/stacks/axon/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +114 -0
  11. package/stacks/axon/templates/.claude/skills/build-automation-workflow/SKILL.md +496 -0
  12. package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/axon-workflow-api.md +279 -0
  13. package/stacks/axon/templates/.claude/skills/build-automation-workflow/references/feature-flag-patterns.md +114 -0
  14. package/stacks/axon/templates/.claude/skills/build-state-change/SKILL.md +396 -0
  15. package/stacks/axon/templates/.claude/skills/build-state-change/references/axon-test-fixture-patterns.md +162 -0
  16. package/stacks/axon/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +56 -0
  17. package/stacks/axon/templates/.claude/skills/build-state-change/references/rest-api-patterns.md +115 -0
  18. package/stacks/axon/templates/.claude/skills/build-state-view/SKILL.md +282 -0
  19. package/stacks/axon/templates/.claude/skills/connect/SKILL.md +178 -0
  20. package/stacks/axon/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +611 -0
  21. package/stacks/axon/templates/.claude/skills/load-slice/SKILL.md +141 -0
  22. package/stacks/axon/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
  23. package/stacks/axon/templates/build-kit/lib/AGENT.md +47 -0
  24. package/stacks/axon/templates/build-kit/lib/agent.sh +20 -0
  25. package/stacks/axon/templates/build-kit/lib/backend-prompt.md +158 -0
  26. package/stacks/axon/templates/build-kit/lib/prompt.md +126 -0
  27. package/stacks/axon/templates/build-kit/lib/ralph.js +369 -0
  28. package/stacks/axon/templates/build-kit/ralph-claude.js +44 -0
  29. package/stacks/axon/templates/build-kit/ralph.sh +98 -0
  30. package/stacks/axon/templates/root/.env.example +5 -0
  31. package/stacks/axon/templates/root/CLAUDE.md +60 -0
  32. package/stacks/axon/templates/root/README.md +44 -0
  33. package/stacks/axon/templates/root/docker-compose.yml +64 -0
  34. package/stacks/axon/templates/root/mvnw +259 -0
  35. package/stacks/axon/templates/root/mvnw.cmd +149 -0
  36. package/stacks/axon/templates/root/pom.xml +125 -0
  37. package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/QuickstartApplication.java +128 -0
  38. package/stacks/axon/templates/root/src/main/java/io/axoniq/quickstart/config/WebConfig.java +155 -0
  39. package/stacks/axon/templates/root/src/main/resources/application.properties +2 -0
  40. package/stacks/axon/templates/root/src/main/resources/static/bg-body.png +0 -0
  41. package/stacks/axon/templates/root/src/main/resources/static/bg-header.png +0 -0
  42. package/stacks/axon/templates/root/src/main/resources/static/index.html +596 -0
  43. package/stacks/cratis-csharp/templates/.claude/skills/_shared/cratis-conventions.md +251 -0
  44. package/stacks/cratis-csharp/templates/.claude/skills/build-automation/SKILL.md +99 -0
  45. package/stacks/cratis-csharp/templates/.claude/skills/build-automation/references/patterns.md +115 -0
  46. package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/SKILL.md +163 -0
  47. package/stacks/cratis-csharp/templates/.claude/skills/build-state-change/references/patterns.md +234 -0
  48. package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/SKILL.md +120 -0
  49. package/stacks/cratis-csharp/templates/.claude/skills/build-state-view/references/patterns.md +166 -0
  50. package/stacks/cratis-csharp/templates/.claude/skills/connect/SKILL.md +169 -0
  51. package/stacks/cratis-csharp/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +609 -0
  52. package/stacks/cratis-csharp/templates/.claude/skills/load-slice/SKILL.md +141 -0
  53. package/stacks/cratis-csharp/templates/.claude/skills/update-slice-status/SKILL.md +105 -0
  54. package/stacks/cratis-csharp/templates/build-kit/lib/AGENT.md +58 -0
  55. package/stacks/cratis-csharp/templates/build-kit/lib/agent.sh +20 -0
  56. package/stacks/cratis-csharp/templates/build-kit/lib/backend-prompt.md +129 -0
  57. package/stacks/cratis-csharp/templates/build-kit/lib/prompt.md +124 -0
  58. package/stacks/cratis-csharp/templates/build-kit/lib/ralph.js +302 -0
  59. package/stacks/cratis-csharp/templates/build-kit/ralph-claude.js +37 -0
  60. package/stacks/cratis-csharp/templates/build-kit/ralph.sh +98 -0
  61. package/stacks/cratis-csharp/templates/root/.frontend/index.css +29 -0
  62. package/stacks/cratis-csharp/templates/root/.frontend/index.html +17 -0
  63. package/stacks/cratis-csharp/templates/root/.frontend/main.tsx +18 -0
  64. package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.json +42 -0
  65. package/stacks/cratis-csharp/templates/root/.frontend/tsconfig.node.json +11 -0
  66. package/stacks/cratis-csharp/templates/root/.frontend/vite.config.ts +56 -0
  67. package/stacks/cratis-csharp/templates/root/App.tsx +23 -0
  68. package/stacks/cratis-csharp/templates/root/CLAUDE.md +62 -0
  69. package/stacks/cratis-csharp/templates/root/CratisApp.csproj +25 -0
  70. package/stacks/cratis-csharp/templates/root/CratisApp.sln +18 -0
  71. package/stacks/cratis-csharp/templates/root/GlobalUsings.cs +3 -0
  72. package/stacks/cratis-csharp/templates/root/Home.tsx +102 -0
  73. package/stacks/cratis-csharp/templates/root/Program.cs +26 -0
  74. package/stacks/cratis-csharp/templates/root/README.md +192 -0
  75. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/AllListings.ts +47 -0
  76. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.cs +11 -0
  77. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/Listing.ts +12 -0
  78. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/ListingDataTable.tsx +17 -0
  79. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Listing/index.ts +1 -0
  80. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Register.ts +51 -0
  81. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/RegisterDialog.tsx +18 -0
  82. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/Registration.cs +27 -0
  83. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/Registration/index.ts +1 -0
  84. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeFeature.tsx +22 -0
  85. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/SomeName.cs +3 -0
  86. package/stacks/cratis-csharp/templates/root/SomeModule/SomeFeature/index.ts +1 -0
  87. package/stacks/cratis-csharp/templates/root/appsettings.Development.json +9 -0
  88. package/stacks/cratis-csharp/templates/root/appsettings.json +26 -0
  89. package/stacks/cratis-csharp/templates/root/docker-compose.yml +23 -0
  90. package/stacks/cratis-csharp/templates/root/package.json +33 -0
  91. package/stacks/cratis-csharp/templates/root/tsconfig.json +3 -0
  92. package/stacks/modeling-kit/templates/.claude/settings.local.json +13 -0
  93. package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +215 -0
  94. package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +176 -0
  95. package/stacks/modeling-kit/templates/.claude/skills/connect/SKILL.md +182 -0
  96. package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +383 -0
  97. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-applying-conways-law/SKILL.md +465 -0
  98. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +587 -0
  99. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/facilitating-event-modeling-workshops.md +626 -0
  100. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +542 -0
  101. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-event-models/SKILL.md +497 -0
  102. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +783 -0
  103. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +604 -0
  104. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +699 -0
  105. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-integrating-legacy-systems/SKILL.md +674 -0
  106. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/SKILL.md +231 -0
  107. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/domain-patterns.md +150 -0
  108. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/patterns.md +329 -0
  109. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-optimizing-stream-design/references/snapshotting.md +204 -0
  110. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +352 -0
  111. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/project-planning-with-event-modeling.md +341 -0
  112. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/SKILL.md +153 -0
  113. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +136 -0
  114. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/examples.md +78 -0
  115. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/patterns.md +85 -0
  116. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +655 -0
  117. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-translating-external-events/SKILL.md +479 -0
  118. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +234 -0
  119. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +353 -0
  120. package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +200 -0
  121. package/stacks/modeling-kit/templates/.claude/skills/handle-comment/SKILL.md +94 -0
  122. package/stacks/modeling-kit/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +441 -0
  123. package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +290 -0
  124. package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +212 -0
  125. package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +123 -0
  126. package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +353 -0
  127. package/stacks/modeling-kit/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
  128. package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +134 -0
  129. package/stacks/modeling-kit/templates/kit/lib/agent.sh +20 -0
  130. package/stacks/modeling-kit/templates/kit/lib/ollama-agent.js +147 -0
  131. package/stacks/modeling-kit/templates/kit/lib/ralph.js +286 -0
  132. package/stacks/modeling-kit/templates/kit/package.json +12 -0
  133. package/stacks/modeling-kit/templates/kit/ralph-claude.js +50 -0
  134. package/stacks/modeling-kit/templates/kit/ralph-ollama.js +38 -0
  135. package/stacks/modeling-kit/templates/kit/ralph.sh +61 -0
  136. package/stacks/modeling-kit/templates/kit/realtime-agent.js +18 -0
  137. package/stacks/modeling-kit/templates/root/CLAUDE.md +56 -0
  138. package/stacks/node/templates/.claude/skills/build-automation/SKILL.md +266 -0
  139. package/stacks/node/templates/.claude/skills/build-state-change/SKILL.md +344 -0
  140. package/stacks/node/templates/.claude/skills/build-state-view/SKILL.md +412 -0
  141. package/stacks/node/templates/.claude/skills/connect/SKILL.md +178 -0
  142. package/stacks/node/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
  143. package/stacks/node/templates/.claude/skills/load-slice/SKILL.md +143 -0
  144. package/stacks/node/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
  145. package/stacks/node/templates/build-kit/README.md +86 -0
  146. package/stacks/node/templates/build-kit/lib/AGENT.md +47 -0
  147. package/stacks/node/templates/build-kit/lib/agent.sh +20 -0
  148. package/stacks/node/templates/build-kit/lib/backend-prompt.md +152 -0
  149. package/stacks/node/templates/build-kit/lib/prompt.md +122 -0
  150. package/stacks/node/templates/build-kit/lib/ralph.js +369 -0
  151. package/stacks/node/templates/build-kit/ralph-claude.js +44 -0
  152. package/stacks/node/templates/build-kit/ralph.sh +98 -0
  153. package/stacks/node/templates/root/.env.example +15 -0
  154. package/stacks/node/templates/root/CLAUDE.md +60 -0
  155. package/stacks/node/templates/root/docker-compose.yml +15 -0
  156. package/stacks/node/templates/root/flyway.conf +17 -0
  157. package/stacks/node/templates/root/migrations/V1__schema.sql.example +12 -0
  158. package/stacks/node/templates/root/package.json +50 -0
  159. package/stacks/node/templates/root/server.ts +130 -0
  160. package/stacks/node/templates/root/setup-env.sh +53 -0
  161. package/stacks/node/templates/root/src/common/assertions.ts +6 -0
  162. package/stacks/node/templates/root/src/common/db.ts +32 -0
  163. package/stacks/node/templates/root/src/common/loadPostgresEventstore.ts +23 -0
  164. package/stacks/node/templates/root/src/common/parseEndpoint.ts +51 -0
  165. package/stacks/node/templates/root/src/common/processorDlq.ts +28 -0
  166. package/stacks/node/templates/root/src/common/realtimeBroadcast.ts +12 -0
  167. package/stacks/node/templates/root/src/common/replay.ts +16 -0
  168. package/stacks/node/templates/root/src/common/routes.ts +19 -0
  169. package/stacks/node/templates/root/src/common/testHelpers.ts +44 -0
  170. package/stacks/node/templates/root/src/swagger.ts +34 -0
  171. package/stacks/node/templates/root/src/util/assertions.ts +6 -0
  172. package/stacks/node/templates/root/src/util/hash.ts +9 -0
  173. package/stacks/node/templates/root/src/util/sanitize.ts +23 -0
  174. package/stacks/node/templates/root/tsconfig.json +32 -0
  175. package/stacks/node/templates/root/vercel.json +8 -0
  176. package/stacks/supabase/templates/.claude/skills/build-automation/SKILL.md +266 -0
  177. package/stacks/supabase/templates/.claude/skills/build-state-change/SKILL.md +344 -0
  178. package/stacks/supabase/templates/.claude/skills/build-state-view/SKILL.md +390 -0
  179. package/stacks/supabase/templates/.claude/skills/build-webhook/SKILL.md +303 -0
  180. package/stacks/supabase/templates/.claude/skills/connect/SKILL.md +178 -0
  181. package/stacks/supabase/templates/.claude/skills/learn-eventmodelers-api/SKILL.md +628 -0
  182. package/stacks/supabase/templates/.claude/skills/load-slice/SKILL.md +143 -0
  183. package/stacks/supabase/templates/.claude/skills/update-slice-status/SKILL.md +110 -0
  184. package/stacks/supabase/templates/build-kit/README.md +86 -0
  185. package/stacks/supabase/templates/build-kit/lib/AGENT.md +47 -0
  186. package/stacks/supabase/templates/build-kit/lib/agent.sh +20 -0
  187. package/stacks/supabase/templates/build-kit/lib/backend-prompt.md +152 -0
  188. package/stacks/supabase/templates/build-kit/lib/prompt.md +123 -0
  189. package/stacks/supabase/templates/build-kit/lib/ralph.js +369 -0
  190. package/stacks/supabase/templates/build-kit/ralph-claude.js +44 -0
  191. package/stacks/supabase/templates/build-kit/ralph.sh +98 -0
  192. package/stacks/supabase/templates/root/.env.example +22 -0
  193. package/stacks/supabase/templates/root/CLAUDE.md +61 -0
  194. package/stacks/supabase/templates/root/flyway.conf +17 -0
  195. package/stacks/supabase/templates/root/package.json +52 -0
  196. package/stacks/supabase/templates/root/server.ts +177 -0
  197. package/stacks/supabase/templates/root/setup-env.sh +53 -0
  198. package/stacks/supabase/templates/root/src/common/assertions.ts +6 -0
  199. package/stacks/supabase/templates/root/src/common/db.ts +32 -0
  200. package/stacks/supabase/templates/root/src/common/loadPostgresEventstore.ts +23 -0
  201. package/stacks/supabase/templates/root/src/common/parseEndpoint.ts +51 -0
  202. package/stacks/supabase/templates/root/src/common/processorDlq.ts +28 -0
  203. package/stacks/supabase/templates/root/src/common/realtimeBroadcast.ts +19 -0
  204. package/stacks/supabase/templates/root/src/common/replay.ts +16 -0
  205. package/stacks/supabase/templates/root/src/common/routes.ts +23 -0
  206. package/stacks/supabase/templates/root/src/common/testHelpers.ts +54 -0
  207. package/stacks/supabase/templates/root/src/supabase/README.md +141 -0
  208. package/stacks/supabase/templates/root/src/supabase/api.ts +56 -0
  209. package/stacks/supabase/templates/root/src/supabase/requireOrgaAdmin.ts +32 -0
  210. package/stacks/supabase/templates/root/src/supabase/requireSysUser.ts +40 -0
  211. package/stacks/supabase/templates/root/src/supabase/requireUser.ts +72 -0
  212. package/stacks/supabase/templates/root/src/swagger.ts +34 -0
  213. package/stacks/supabase/templates/root/src/util/assertions.ts +6 -0
  214. package/stacks/supabase/templates/root/src/util/hash.ts +9 -0
  215. package/stacks/supabase/templates/root/src/util/sanitize.ts +23 -0
  216. package/stacks/supabase/templates/root/supabase/config.toml +295 -0
  217. package/stacks/supabase/templates/root/supabase/migrations/V1__schema.sql.example +12 -0
  218. package/stacks/supabase/templates/root/supabase/seed.sql +1 -0
  219. package/stacks/supabase/templates/root/tsconfig.json +32 -0
  220. package/stacks/supabase/templates/root/vercel.json +8 -0
@@ -0,0 +1,115 @@
1
+ # REST API Patterns
2
+
3
+ Ground truth: `RegisterCustomerRestController`
4
+ (`src/main/java/.../foo/register/RegisterCustomerRestController.java`), this project's only REST
5
+ controller for a write slice. This project uses WebFlux (`spring-boot-starter-webflux`), not
6
+ `spring-boot-starter-web` — controller methods return `Mono<ResponseEntity<...>>`, not `ResponseEntity`
7
+ directly, and tests use `WebTestClient`, not `MockMvc`.
8
+
9
+ ## Controller
10
+
11
+ ```java
12
+ package io.axoniq.quickstart.slices.{context}.{slicename};
13
+
14
+ import org.axonframework.messaging.commandhandling.gateway.CommandGateway;
15
+ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
16
+ import org.springframework.http.ResponseEntity;
17
+ import org.springframework.web.bind.annotation.PostMapping;
18
+ import org.springframework.web.bind.annotation.RequestBody;
19
+ import org.springframework.web.bind.annotation.RestController;
20
+ import reactor.core.publisher.Mono;
21
+
22
+ @RestController
23
+ @ConditionalOnProperty(prefix = "slices.{context}.write", name = "{slicename}.enabled")
24
+ public class {SliceName}RestController {
25
+
26
+ private final CommandGateway commandGateway;
27
+
28
+ public {SliceName}RestController(CommandGateway commandGateway) {
29
+ this.commandGateway = commandGateway;
30
+ }
31
+
32
+ @PostMapping("/api/{context}/{resource}")
33
+ public Mono<ResponseEntity<Void>> handle(@RequestBody {SliceName}RequestBody body) {
34
+ var command = new {SliceName}Command(body.field1(), body.idField());
35
+ return Mono.fromFuture(commandGateway.send(command).getResultMessage())
36
+ .map(ignored -> ResponseEntity.ok().<Void>build())
37
+ .onErrorResume(e -> Mono.just(ResponseEntity.badRequest().<Void>build()));
38
+ }
39
+
40
+ public record {SliceName}RequestBody(String field1, String idField) {}
41
+ }
42
+ ```
43
+
44
+ Notes, all taken directly from the real controller:
45
+ - `CommandGateway.send(command)` returns a `CommandResult`; `.getResultMessage()` on it returns a
46
+ `CompletableFuture<? extends Message>` — wrap with `Mono.fromFuture(...)`, don't call `.get()`/`.join()`.
47
+ - Success → `200` with an empty body (`ResponseEntity.ok().<Void>build()`); any exception from the
48
+ command handler (e.g. the `IllegalStateException` a business-rule violation throws) → `400` via
49
+ `onErrorResume`. There's no per-exception-type branching here — every failure maps to `400`.
50
+ - The request body record (`{SliceName}RequestBody`) is a plain nested record matching the command's
51
+ fields — it is not the Command itself; the controller maps one to the other explicitly.
52
+ - `@ConditionalOnProperty` uses the exact same `prefix`/`name` pair as the command handler (see
53
+ Step 6 in `SKILL.md`) — both gate on the same flag.
54
+
55
+ ## Test — `WebTestClient` (not `MockMvc`)
56
+
57
+ No REST test exists yet in this repo for `RegisterCustomerRestController` — this is the recommended
58
+ shape for the first one, matching the project's WebFlux stack and the `@ConditionalOnProperty`
59
+ feature-flag convention (tests disable all slices by default; opt in per test class):
60
+
61
+ ```java
62
+ package io.axoniq.quickstart.slices.{context}.{slicename};
63
+
64
+ import org.axonframework.messaging.commandhandling.gateway.CommandGateway;
65
+ import org.junit.jupiter.api.Test;
66
+ import org.springframework.beans.factory.annotation.Autowired;
67
+ import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
68
+ import org.springframework.boot.test.context.TestConfiguration;
69
+ import org.springframework.boot.test.mock.mockito.MockBean;
70
+ import org.springframework.context.annotation.Bean;
71
+ import org.springframework.test.context.TestPropertySource;
72
+ import org.springframework.test.web.reactive.server.WebTestClient;
73
+
74
+ import static org.mockito.ArgumentMatchers.any;
75
+ import static org.mockito.Mockito.doReturn;
76
+ import static org.mockito.Mockito.verify;
77
+
78
+ @WebFluxTest({SliceName}RestController.class)
79
+ @TestPropertySource(properties = "slices.{context}.write.{slicename}.enabled=true")
80
+ class {SliceName}RestControllerTest {
81
+
82
+ @Autowired
83
+ private WebTestClient webTestClient;
84
+
85
+ @MockBean
86
+ private CommandGateway commandGateway;
87
+
88
+ @Test
89
+ void acceptsValidRequest() {
90
+ var resultMessage = /* stub a CompletableFuture<CommandResultMessage<?>> completing normally */;
91
+ doReturn(resultMessage).when(commandGateway).send(any()).getResultMessage();
92
+
93
+ webTestClient.post().uri("/api/{context}/{resource}")
94
+ .bodyValue(new {SliceName}RestController.{SliceName}RequestBody("value1", "id-1"))
95
+ .exchange()
96
+ .expectStatus().isOk();
97
+
98
+ verify(commandGateway).send(new {SliceName}Command("value1", "id-1"));
99
+ }
100
+
101
+ @Test
102
+ void mapsHandlerFailureTo400() {
103
+ var resultMessage = /* stub a CompletableFuture failing with IllegalStateException */;
104
+ doReturn(resultMessage).when(commandGateway).send(any()).getResultMessage();
105
+
106
+ webTestClient.post().uri("/api/{context}/{resource}")
107
+ .bodyValue(new {SliceName}RestController.{SliceName}RequestBody("value1", "id-1"))
108
+ .exchange()
109
+ .expectStatus().isBadRequest();
110
+ }
111
+ }
112
+ ```
113
+
114
+ `CommandGateway.send(cmd)` returns `CommandResult`; stub with `doReturn(...).when(mock).getResultMessage()`
115
+ — `when(mock.send(...)).thenReturn(...)` hits a wildcard-capture generics compile error on this API.
@@ -0,0 +1,282 @@
1
+ ---
2
+ name: build-state-view
3
+ authors:
4
+ - Mateusz Nowak
5
+ - Martin Dilger
6
+ description: >
7
+ Implement read slices (projections + query handlers + REST API + tests) using Axon Framework 5
8
+ with Spring Boot. A read slice is: Events projected into a Read Model, queried via QueryGateway.
9
+ Use when: (1) implementing a new read slice / projection in an AF5 Java project,
10
+ (2) migrating/porting a read slice from Axon Framework 4 (Java or Kotlin) to AF5,
11
+ (3) user provides a read slice specification or Event Modeling artifact and asks to implement it,
12
+ (4) user says "implement", "create", "add" a read slice, projection, query handler,
13
+ or read model in an Axon Framework 5 / Vertical Slice Architecture project.
14
+ ---
15
+
16
+ # Axon Framework 5 — Read Slice (Java)
17
+
18
+ ## Step 0: Discover Target Project Conventions
19
+
20
+ > **Comments & description**: Each element in the slice carries a `comments: string[]` array and a `description` field. Use these as implementation hints. When done, resolve each used comment: `POST <BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/<nodeId>/comments/<commentId>/resolve` (get IDs first via GET on same path).
21
+
22
+ Before writing any code, read the target project's `CLAUDE.md`
23
+
24
+ ## Step 1: Ensure Events Exist
25
+
26
+ Before implementing the read slice, verify that all events the projector handles exist in the
27
+ codebase. If they don't, create them **first**.
28
+
29
+ ### Concrete event records
30
+
31
+ ```java
32
+ @Event(namespace = "{Context}", name = "{EventName}", version = "1.0.0")
33
+ public record {EventName}(
34
+ @EventTag
35
+ String {tagProperty},
36
+ String field1
37
+ ){}
38
+ ```
39
+
40
+ Key rules:
41
+ - Import `@Event` from `org.axonframework.messaging.eventhandling.annotation.Event`
42
+ - `namespace` = context name, `name` = record name, `version` = `"1.0.0"` for new events
43
+ - When an event participates in a DCB, add extra `@EventTag` on cross-stream fields
44
+
45
+ ## Step 2: Implement the Read Slice
46
+
47
+ If the Event Modeling artifact includes slice details with `## Scenarios (GWTs)`, use them to
48
+ derive test cases. GWT format for read slices: `Given (events) → Then (information)` — no When.
49
+ Events in Given tell you which events the projector handles. The information element in Then
50
+ describes the expected query result.
51
+
52
+ If the slice description or comments contain `## Implementation Guidelines`, **follow them**.
53
+
54
+ ### Query annotation
55
+
56
+ Every query record must have `@Query(namespace, name, version)`:
57
+
58
+ ```java
59
+ @Query(namespace = "{Context}", name = "Get{SliceName}", version = "1.0.0")
60
+ public record Get{SliceName}(String {filterField}) {
61
+ public record Result(List<{SliceName}Summary> items) {}
62
+ }
63
+ ```
64
+
65
+ - Import `@Query` from `org.axonframework.messaging.queryhandling.annotation.Query`
66
+
67
+ A read slice lives in a single package. **Do NOT add Domain/Application/Presentation section
68
+ comments** — those are only for write slices.
69
+
70
+ ### Slice package structure
71
+
72
+ ```
73
+ de/<package>/{context}/slices/{slicename}/
74
+ ├── Get{SliceName}.java ← query record + nested Result
75
+ ├── {SliceName}Summary.java ← read model (projection output shape)
76
+ ├── {SliceName}Projector.java ← @Component with @EventHandler + @QueryHandler
77
+ └── {SliceName}RestApi.java ← @RestController (if REST chosen)
78
+ ```
79
+
80
+ ### Projector + query handler (JPA-backed)
81
+
82
+ Projections persist to a database via Spring Data JPA — this is the only supported style.
83
+
84
+ ```java
85
+ public record AllCustomersSummary(String name, String email) {}
86
+
87
+ @Query(namespace = "CustomerManagement", name = "GetAllCustomers", version = "1.0.0")
88
+ public record GetAllCustomers() {
89
+ public record Result(List<AllCustomersSummary> items) {}
90
+ }
91
+
92
+ @Entity
93
+ @Table(name = "customer_management_allcustomers")
94
+ class AllCustomersEntity {
95
+
96
+ @Id
97
+ private String email;
98
+ private String name;
99
+
100
+ protected AllCustomersEntity() {
101
+ }
102
+
103
+ AllCustomersEntity(String email, String name) {
104
+ this.email = email;
105
+ this.name = name;
106
+ }
107
+
108
+ AllCustomersSummary toSummary() {
109
+ return new AllCustomersSummary(name, email);
110
+ }
111
+ }
112
+
113
+ @Component
114
+ public class AllCustomersProjector {
115
+
116
+ private final AllCustomersRepository repository;
117
+
118
+ public AllCustomersProjector(AllCustomersRepository repository) {
119
+ this.repository = repository;
120
+ }
121
+
122
+ @EventHandler
123
+ public void on(CustomerRegistered event) {
124
+ repository.save(new AllCustomersEntity(event.email(), event.name()));
125
+ }
126
+
127
+ @QueryHandler
128
+ public GetAllCustomers.Result handle(GetAllCustomers query) {
129
+ List<AllCustomersSummary> items = repository.findAll().stream()
130
+ .map(AllCustomersEntity::toSummary)
131
+ .toList();
132
+ return new GetAllCustomers.Result(items);
133
+ }
134
+ }
135
+
136
+ interface AllCustomersRepository extends JpaRepository<AllCustomersEntity, String> {
137
+ }
138
+ ```
139
+
140
+ ### Result DTO rules
141
+
142
+ - If the read model matches the query result **1:1**, expose the summary record directly.
143
+ - If the read model contains fields the caller already knows from the query (e.g., the filter field),
144
+ omit those from the `Result` and map from the projector's internal model.
145
+
146
+ ### Entity + repository template
147
+
148
+ For filtered queries, add an index and a derived-query method instead of `findAll()`:
149
+
150
+ ```java
151
+ @Entity
152
+ @Table(
153
+ name = "{context}_read_{slicename}",
154
+ indexes = {@Index(name = "idx_{context}_{slicename}_{col}", columnList = "{filterField}")}
155
+ )
156
+ public class {SliceName}Entity {
157
+ @Id private String id;
158
+ private String {filterField};
159
+ // ... other fields
160
+ }
161
+
162
+ @Repository
163
+ interface {SliceName}Repository extends JpaRepository<{SliceName}Entity, String> {
164
+ List<{SliceName}Entity> findAllBy{FilterField}(String {filterField});
165
+ }
166
+ ```
167
+
168
+ Use `findAllBy{FilterField}(...)` in the `@QueryHandler` — DB-level filtering, not client-side.
169
+
170
+ ## Step 3: REST API Exposure (Optional)
171
+
172
+ Check the target project's convention first.
173
+
174
+ ```java
175
+ // File: {SliceName}RestApi.java
176
+ @RestController
177
+ public class {SliceName}RestApi {
178
+
179
+ private final QueryGateway queryGateway;
180
+
181
+ public {SliceName}RestApi(QueryGateway queryGateway) {
182
+ this.queryGateway = queryGateway;
183
+ }
184
+
185
+ @GetMapping("/api/{context}/{filterField}")
186
+ public Mono<Get{SliceName}.Result> query(@PathVariable String {filterField}) {
187
+ return Mono.fromFuture(
188
+ queryGateway.query(new Get{SliceName}({filterField}), Get{SliceName}.Result.class)
189
+ );
190
+ }
191
+ }
192
+ ```
193
+
194
+ ## Step 4: Design Test Cases
195
+
196
+ Implement the test cases provided in the slice definition.
197
+ Do not design your own test cases unless specifically instructed to do so.
198
+
199
+ ### Mapping GWT Scenarios to Tests
200
+
201
+ | GWT Element | Test Code |
202
+ |---|---|
203
+ | `NOTHING` in Given | instantiate projector, call `handle(query)` directly |
204
+ | Event in Given | call `projector.on(event)` |
205
+ | Information in Then | `assertThat(result.items()).containsExactlyInAnyOrder(...)` |
206
+
207
+ ## Step 5: Implement the Slice Test
208
+
209
+ Pure unit tests — instantiate the projector directly, no Spring context needed.
210
+ Fast, no container startup.
211
+
212
+ ```java
213
+ // File: src/test/java/de/<package>/{context}/slices/{slicename}/{SliceName}ProjectorTest.java
214
+ class {SliceName}ProjectorTest {
215
+
216
+ private {SliceName}Projector projector;
217
+
218
+ @BeforeEach
219
+ void setUp() {
220
+ projector = new {SliceName}Projector();
221
+ }
222
+
223
+ @Test
224
+ @DisplayName("given no events, when query, then empty result")
225
+ void emptyState() {
226
+ var result = projector.handle(new Get{SliceName}("filter-value"));
227
+
228
+ assertThat(result.items()).isEmpty();
229
+ }
230
+
231
+ @Test
232
+ @DisplayName("given creation event, then item appears in result")
233
+ void creationEvent() {
234
+ projector.on(new {CreationEvent}("id-1", "filter-value" /*, other fields */));
235
+
236
+ var result = projector.handle(new Get{SliceName}("filter-value"));
237
+
238
+ assertThat(result.items()).containsExactly(
239
+ new {SliceName}Summary("id-1", "filter-value" /*, expected fields */)
240
+ );
241
+ }
242
+
243
+ @Test
244
+ @DisplayName("given creation then deletion, then item disappears")
245
+ void deletionEvent() {
246
+ projector.on(new {CreationEvent}("id-1", "filter-value"));
247
+ projector.on(new {DeletionEvent}("id-1"));
248
+
249
+ var result = projector.handle(new Get{SliceName}("filter-value"));
250
+
251
+ assertThat(result.items()).isEmpty();
252
+ }
253
+
254
+ @Test
255
+ @DisplayName("items are isolated by filter field")
256
+ void isolation() {
257
+ projector.on(new {CreationEvent}("id-1", "group-A"));
258
+ projector.on(new {CreationEvent}("id-2", "group-B"));
259
+
260
+ assertThat(projector.handle(new Get{SliceName}("group-A")).items()).hasSize(1);
261
+ assertThat(projector.handle(new Get{SliceName}("group-B")).items()).hasSize(1);
262
+ }
263
+ }
264
+ ```
265
+
266
+ ### Key Rules
267
+
268
+ - **Metadata when needed**: If the projector uses `@MetadataValue(...)`, pass metadata by publishing
269
+ events to an `AxonTestFixture` rather than calling `on(event)` directly. Use the Spring Boot
270
+ integration test approach in that case.
271
+ - **Assert with full objects**: Use `containsExactlyInAnyOrder(new Summary(...))` rather than
272
+ field-by-field assertions — catches serialization mismatches.
273
+
274
+ ## Final Verification: Does the Implementation Match slice.json?
275
+
276
+ Before marking this slice as `Done`, verify the implementation against slice.json:
277
+
278
+ - [ ] Every field in the read model / query result definition in slice.json has a field in `{SliceName}Summary` — no invented fields
279
+ - [ ] Every event type in `events[]` has an `@EventHandler` in the projector — no events missed or assumed
280
+ - [ ] Every GWT scenario in `specifications[]` maps to a test case in `{SliceName}ProjectorTest`
281
+ - [ ] No extra query parameters or filter logic were added beyond what slice.json defines
282
+ - [ ] No field names were assumed or guessed — if a field is not in slice.json, it is not in the code
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: connect
3
+ description: Resolve eventmodelers connection config (token, boardId, baseUrl) from inline params or .eventmodelers/config.json — ask the user for missing values, persist them, and add the file to .gitignore. All other skills invoke this first.
4
+ ---
5
+
6
+ # Connect — Resolve Eventmodelers Config
7
+
8
+ **Every other skill invokes this skill first** before making any API calls. Do not proceed past this skill until all four values (`TOKEN`, `BOARD_ID`, `ORG_ID`, `BASE_URL`) are resolved.
9
+
10
+ ---
11
+
12
+ ## What this skill produces
13
+
14
+ After running, the following variables are available for the rest of the session:
15
+
16
+ | Variable | Header sent to API | Description |
17
+ |----------|--------------------|-------------|
18
+ | `TOKEN` | `x-token` | API token UUID |
19
+ | `BOARD_ID` | `x-board-id` | Target board UUID |
20
+ | `ORG_ID` | — | Organization UUID (used in all board-scoped URLs) |
21
+ | `BASE_URL` | — | Base URL, e.g. `http://localhost:3000` |
22
+
23
+ Every API call in every skill must include these headers:
24
+ ```
25
+ x-token: <TOKEN>
26
+ x-board-id: <BOARD_ID>
27
+ x-user-id: <skill-name> ← set by each skill individually
28
+ ```
29
+
30
+ All board-scoped URLs follow the pattern: `<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/...`
31
+
32
+ ---
33
+
34
+ ## Step 0 — Check for inline parameters
35
+
36
+ Before reading the config file, scan the prompt/arguments that invoked this skill for inline overrides. Supported formats:
37
+
38
+ | Pattern | Example |
39
+ |---------|---------|
40
+ | `board=<uuid>` | `board=05cda19d-d5b8-4b51-ae88-c72f2611548a` |
41
+ | `token=<uuid>` | `token=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` |
42
+ | `org=<uuid>` | `org=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` |
43
+ | `baseUrl=<url>` | `baseUrl=http://localhost:3000` |
44
+
45
+ If an inline `board=<uuid>` is found, use it as `BOARD_ID` — **it takes priority over the config file**. Same for `token`, `org`, and `baseUrl`. Record which values came from inline params so they are not overwritten in Step 3.
46
+
47
+ ---
48
+
49
+ ## Step 1 — Read config file
50
+
51
+ Search for `.eventmodelers/config.json` starting from the current working directory and walking up through all parent directories:
52
+
53
+ ```bash
54
+ dir="$PWD"
55
+ config_file=""
56
+ while [ "$dir" != "/" ]; do
57
+ if [ -f "$dir/.eventmodelers/config.json" ]; then
58
+ config_file="$dir/.eventmodelers/config.json"
59
+ break
60
+ fi
61
+ dir="$(dirname "$dir")"
62
+ done
63
+ [ -n "$config_file" ] && cat "$config_file"
64
+ ```
65
+
66
+ If a file is found (at any level), note its path and extract any values **not already set by Step 0**:
67
+ - `token` → `TOKEN`
68
+ - `boardId` → `BOARD_ID`
69
+ - `organizationId` → `ORG_ID`
70
+ - `baseUrl` → `BASE_URL` (default: `https://api.eventmodelers.ai` if missing)
71
+
72
+ Resolution priority: **inline param > config file > ask user**
73
+
74
+ If all four are present (from any source), skip to **Step 4 — Verify**.
75
+
76
+ ---
77
+
78
+ ## Step 2 — Ask for missing values
79
+
80
+ If after Steps 0 and 1 any required field is still missing, **ask the user one question first**:
81
+
82
+ > "Do you have a config from the eventmodelers accounts page? (yes / no)"
83
+
84
+ **If the user answers yes:**
85
+ Stop asking questions. Show this hint and wait for them to paste:
86
+
87
+ > "Great — please paste your config from https://app.eventmodelers.ai/account here."
88
+
89
+ When they paste a JSON object, parse it immediately — accept both `orgId` and `organizationId` as the organization field — apply all values, and proceed directly to Step 3.
90
+
91
+ **If the user answers no** (or pastes only a partial config), ask for each still-missing field one at a time, in this order: `token`, then `boardId`, then `orgId`. Wait for the answer before asking the next.
92
+
93
+ | Field | What to ask |
94
+ |-------|-------------|
95
+ | `token` | "Please provide your eventmodelers API token (a UUID from your workspace settings)." |
96
+ | `boardId` | "Please provide the board ID you want to work with (the UUID from the board URL)." |
97
+ | `orgId` | "Please provide your organization ID (the UUID from your organization settings)." |
98
+ | `baseUrl` | Do **not** ask — default to `https://api.eventmodelers.ai` silently. |
99
+
100
+ Where to find the token: users generate API tokens in their workspace settings at the eventmodelers platform. The token is shown only once at creation time. It is a UUID and must belong to the same organization as the board.
101
+
102
+ ---
103
+
104
+ ## Step 3 — Persist config
105
+
106
+ Once all values are collected, write the config file. When writing, merge with any existing config — do **not** overwrite fields that were provided as inline params with values from a previous config (the inline param is the user's explicit intent for this session, but the persisted value should reflect the most recently user-supplied value):
107
+
108
+ ```bash
109
+ mkdir -p .eventmodelers
110
+ cat > .eventmodelers/config.json << 'EOF'
111
+ {
112
+ "token": "<TOKEN>",
113
+ "boardId": "<BOARD_ID>",
114
+ "orgId": "<ORG_ID>",
115
+ "baseUrl": "<BASE_URL>"
116
+ }
117
+ EOF
118
+ ```
119
+
120
+ Then ensure `.eventmodelers/config.json` is in `.gitignore`. Check whether it is already present:
121
+
122
+ ```bash
123
+ grep -q ".eventmodelers/config.json" .gitignore 2>/dev/null || echo "MISSING"
124
+ ```
125
+
126
+ If `MISSING`, append it:
127
+
128
+ ```bash
129
+ echo ".eventmodelers/config.json" >> .gitignore
130
+ ```
131
+
132
+ Tell the user: `"Config saved to .eventmodelers/config.json and added to .gitignore."`
133
+
134
+ ---
135
+
136
+ ## Step 4 — Verify
137
+
138
+ Confirm the token and board are valid with a lightweight call:
139
+
140
+ ```bash
141
+ curl -s -o /dev/null -w "%{http_code}" \
142
+ -H "x-token: <TOKEN>" \
143
+ -H "x-board-id: <BOARD_ID>" \
144
+ -H "x-user-id: connect-skill" \
145
+ "<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes?type=CHAPTER"
146
+ ```
147
+
148
+ | Response | Action |
149
+ |----------|--------|
150
+ | `200` | Config is valid. Print one line: `"Connected — board <BOARD_ID>"` and return. |
151
+ | `401` | Token is invalid or missing. Tell the user and re-run from Step 2, clearing `token`. |
152
+ | `403` | Token organization does not match board. Tell the user to check that the token was issued for the correct workspace. Re-run from Step 2 for both fields. |
153
+ | `404` | Board not found. Tell the user and re-run from Step 2, clearing `boardId`. |
154
+ | Any other | Print the status code and raw response. Ask the user how to proceed. |
155
+
156
+ ---
157
+
158
+ ## Config file format
159
+
160
+ `.eventmodelers/config.json`:
161
+ ```json
162
+ {
163
+ "token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
164
+ "boardId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
165
+ "orgId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
166
+ "baseUrl": "http://localhost:3000"
167
+ }
168
+ ```
169
+
170
+ The `token` field is a secret. It is never logged or shown after initial confirmation.
171
+
172
+ ---
173
+
174
+ ## Security notes
175
+
176
+ - The config file is workspace-local and gitignored — never commit it.
177
+ - The token grants write access to all boards in its organization — treat it like a password.
178
+ - If a skill receives a `401` or `403` mid-session, re-invoke this skill to refresh the config before retrying.