@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,369 @@
1
+ // Common runtime for the ralph loop + realtime agent.
2
+ // Not meant to be run directly — use ralph-claude.js or ralph-ollama.js.
3
+ //
4
+ // startRalph({ kitDir, projectDir, onTask, onPlannedSlice })
5
+ // onTask(prompt) — called when tasks.json has entries
6
+ // onPlannedSlice(prompt) — called when .slices/ has a "Planned" entry (omit to skip)
7
+
8
+ import { createClient } from '@supabase/supabase-js';
9
+ import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync } from 'fs';
10
+ import { join, dirname } from 'path';
11
+ import { randomUUID } from 'crypto';
12
+
13
+ // ── HTTP helpers ──────────────────────────────────────────────────────────────
14
+
15
+ class HttpError extends Error {
16
+ constructor(status, body) {
17
+ super(`HTTP ${status}: ${body}`);
18
+ this.status = status;
19
+ }
20
+ }
21
+
22
+ async function fetchJSON(url, options) {
23
+ const res = await fetch(url, options);
24
+ if (!res.ok) throw new HttpError(res.status, await res.text());
25
+ return res.json();
26
+ }
27
+
28
+ async function retryOn401(label, fn, maxRetries = 3) {
29
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
30
+ try {
31
+ return await fn();
32
+ } catch (err) {
33
+ if (err instanceof HttpError && err.status === 401) {
34
+ if (attempt < maxRetries) {
35
+ console.warn(`[agent] ${label} — 401, retrying (${attempt}/${maxRetries})...`);
36
+ continue;
37
+ }
38
+ console.error(`[agent] ${label} — 401 after ${maxRetries} retries, shutting down`);
39
+ process.exit(1);
40
+ }
41
+ throw err;
42
+ }
43
+ }
44
+ }
45
+
46
+ // ── Config ────────────────────────────────────────────────────────────────────
47
+
48
+ // Config is resolved by walking from the kit dir up through every ancestor
49
+ // directory's .eventmodelers/config.json, merging fields as we go — a value
50
+ // set by a closer (more specific) directory always wins over a farther one.
51
+ // The walk stops as soon as the merged config has full connection credentials
52
+ // (see hasCredentials); anthropicBaseUrl/model are picked up opportunistically
53
+ // along the way but never force the walk to continue further up.
54
+ function* configCandidates(kitDir) {
55
+ yield join(kitDir, '.eventmodelers', 'config.json');
56
+ let dir = dirname(kitDir);
57
+ while (true) {
58
+ yield join(dir, '.eventmodelers', 'config.json');
59
+ const parent = dirname(dir);
60
+ if (parent === dir) return;
61
+ dir = parent;
62
+ }
63
+ }
64
+
65
+ function loadLocalConfig(kitDir) {
66
+ const merged = {};
67
+ const sources = [];
68
+
69
+ for (const candidate of configCandidates(kitDir)) {
70
+ if (!existsSync(candidate)) continue;
71
+ let cfg;
72
+ try {
73
+ cfg = JSON.parse(readFileSync(candidate, 'utf-8'));
74
+ } catch {
75
+ console.warn(`[ralph] Skipping invalid config at ${candidate}`);
76
+ continue;
77
+ }
78
+ for (const [key, value] of Object.entries(cfg)) {
79
+ if (merged[key] === undefined) merged[key] = value;
80
+ }
81
+ sources.push(candidate);
82
+ if (hasCredentials(merged)) break;
83
+ }
84
+
85
+ if (process.env.BASE_URL) merged.baseUrl = process.env.BASE_URL;
86
+
87
+ if (sources.length > 1) {
88
+ console.log(`[ralph] Merged config from: ${sources.join(', ')}`);
89
+ } else if (sources.length === 1 && sources[0] !== join(kitDir, '.eventmodelers', 'config.json')) {
90
+ console.log(`[ralph] Using credentials from ${sources[0]}`);
91
+ } else if (sources.length === 0) {
92
+ console.warn(`[ralph] Note: no .eventmodelers/config.json found — platform sync disabled.`);
93
+ console.warn(` To enable board sync, follow: https://app.eventmodelers.ai/documentation#build-supabase`);
94
+ console.warn(` Code generation from local slice definitions will still run.`);
95
+ }
96
+
97
+ return merged;
98
+ }
99
+
100
+ function hasCredentials(cfg) {
101
+ return !!(cfg.token && cfg.organizationId && cfg.boardId && cfg.baseUrl);
102
+ }
103
+
104
+ async function fetchPlatformConfig(local) {
105
+ const remote = await fetchJSON(`${local.baseUrl}/api/config`, {
106
+ headers: { 'x-token': local.token },
107
+ });
108
+ return { ...local, ...remote };
109
+ }
110
+
111
+ // ── Realtime agent ────────────────────────────────────────────────────────────
112
+
113
+ async function getRealtimeToken(cfg) {
114
+ const { token } = await fetchJSON(
115
+ `${cfg.baseUrl}/api/org/${cfg.organizationId}/prompts/realtime-token`,
116
+ { headers: { 'x-token': cfg.token } },
117
+ );
118
+ return token;
119
+ }
120
+
121
+ function slugify(str) {
122
+ return str.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
123
+ }
124
+
125
+ async function fetchAndPersistSlices(cfg, kitDir) {
126
+ const url = `${cfg.baseUrl}/api/org/${cfg.organizationId}/boards/${cfg.boardId}/slicedata/slices`;
127
+ const { slices } = await fetchJSON(url, {
128
+ headers: { 'x-token': cfg.token, 'x-board-id': cfg.boardId },
129
+ });
130
+ const slicesDir = join(kitDir, '.slices');
131
+ mkdirSync(slicesDir, { recursive: true });
132
+
133
+ // Group by context slug
134
+ const contexts = {};
135
+ for (const slice of slices) {
136
+ const contextSlug = slice.contextName ? slugify(slice.contextName) : 'default';
137
+ if (!contexts[contextSlug]) contexts[contextSlug] = { name: slice.contextName || 'default', slices: [] };
138
+ contexts[contextSlug].slices.push(slice);
139
+ }
140
+
141
+ // current_context.json is STICKY. We work within ONE context at a time and must
142
+ // not auto-jump to another context just because it happens to have planned work.
143
+ // Keep the existing context if it still exists; only seed it when absent or stale.
144
+ const ctxPath = join(slicesDir, 'current_context.json');
145
+ let activeCtx = null;
146
+ if (existsSync(ctxPath)) {
147
+ try { activeCtx = JSON.parse(readFileSync(ctxPath, 'utf-8')).name; } catch {}
148
+ }
149
+ if (!activeCtx || !contexts[activeCtx]) {
150
+ // First run (or the current context disappeared): seed with a context that
151
+ // has planned work, else the first one. This is the ONLY place we choose it.
152
+ const plannedCtx = Object.keys(contexts).find(c => contexts[c].slices.some(s => (s.status || '').toLowerCase() === 'planned'));
153
+ activeCtx = plannedCtx || Object.keys(contexts)[0] || 'default';
154
+ writeFileSync(ctxPath, JSON.stringify({ name: activeCtx }, null, 2), 'utf-8');
155
+ }
156
+
157
+ // Write per-context index.json and per-slice slice.json
158
+ for (const [contextSlug, { slices: ctxSlices }] of Object.entries(contexts)) {
159
+ const contextDir = join(slicesDir, contextSlug);
160
+ mkdirSync(contextDir, { recursive: true });
161
+
162
+ const indexSlices = ctxSlices.map((s, i) => {
163
+ const folder = (s.title ?? s.id).replaceAll(' ', '').toLowerCase();
164
+ return {
165
+ id: s.id,
166
+ slice: s.title,
167
+ index: i,
168
+ contextName: s.contextName || contextSlug,
169
+ contextSlug,
170
+ folder,
171
+ status: s.status,
172
+ definition: { id: s.id, title: s.title, status: s.status },
173
+ };
174
+ });
175
+ writeFileSync(join(contextDir, 'index.json'), JSON.stringify({ slices: indexSlices }, null, 2), 'utf-8');
176
+
177
+ for (const slice of ctxSlices) {
178
+ const folder = (slice.title ?? slice.id).replaceAll(' ', '').toLowerCase();
179
+ const sliceDir = join(contextDir, folder);
180
+ mkdirSync(sliceDir, { recursive: true });
181
+ writeFileSync(join(sliceDir, 'slice.json'), JSON.stringify(slice, null, 2), 'utf-8');
182
+ }
183
+ }
184
+
185
+ console.log(`[agent] Persisted ${slices.length} slice(s)`);
186
+ }
187
+
188
+ async function writeTask(payload, kitDir) {
189
+ const tasksPath = join(kitDir, 'tasks.json');
190
+ const existing = existsSync(tasksPath) ? JSON.parse(readFileSync(tasksPath, 'utf-8')) : [];
191
+ const filtered = existing.filter(t => t.payload?.sliceId !== payload.sliceId);
192
+ const task = { id: randomUUID(), createdAt: new Date().toISOString(), payload };
193
+ filtered.push(task);
194
+ writeFileSync(tasksPath, JSON.stringify(filtered, null, 2), 'utf-8');
195
+ console.log(`[agent] Task written — slice="${payload.sliceTitle}" status="${payload.sliceStatus}"`);
196
+ }
197
+
198
+ async function startRealtimeAgent(cfg, kitDir) {
199
+ let realtimeToken = await retryOn401('getRealtimeToken', () => getRealtimeToken(cfg));
200
+
201
+ await retryOn401('fetchAndPersistSlices', () => fetchAndPersistSlices(cfg, kitDir)).catch((err) =>
202
+ console.error('[agent] Initial slice fetch error:', err),
203
+ );
204
+
205
+ const supabase = createClient(cfg.supabaseUrl, cfg.supabaseAnonKey, {
206
+ realtime: { params: { apikey: cfg.supabaseAnonKey } },
207
+ });
208
+ await supabase.realtime.setAuth(realtimeToken);
209
+
210
+ const channelName = `board:${cfg.boardId}-slicechanged`;
211
+
212
+ supabase
213
+ .channel(channelName, { config: { private: true } })
214
+ .on('broadcast', { event: 'message' }, (msg) => {
215
+ if (msg.payload === 'Exit') {
216
+ console.log('[agent] Received "Exit" — shutting down');
217
+ process.exit(0);
218
+ }
219
+ })
220
+ .on('broadcast', { event: 'slice:changed' }, async (msg) => {
221
+ const payload = msg.payload;
222
+ console.log(`[agent] slice:changed — slice="${payload.sliceTitle}" status="${payload.sliceStatus}"`);
223
+ await retryOn401('fetchAndPersistSlices', () => fetchAndPersistSlices(cfg, kitDir)).catch((err) =>
224
+ console.error('[agent] Slice persist error:', err),
225
+ );
226
+ // Planned slices are handled by onPlannedSlice directly — no task needed
227
+ if ((payload.sliceStatus || '').toLowerCase() !== 'planned') {
228
+ await writeTask(payload, kitDir).catch((err) => console.error('[agent] writeTask error:', err));
229
+ }
230
+ })
231
+ .subscribe((status) => console.log(`[agent] Channel "${channelName}": ${status}`));
232
+
233
+ setInterval(async () => {
234
+ try {
235
+ realtimeToken = await retryOn401('getRealtimeToken (refresh)', () => getRealtimeToken(cfg));
236
+ supabase.realtime.setAuth(realtimeToken);
237
+ console.log('[agent] Token refreshed');
238
+ } catch (err) {
239
+ console.error('[agent] Token refresh failed:', err);
240
+ }
241
+ }, 10 * 60 * 1000);
242
+
243
+ const ping = async () => {
244
+ try {
245
+ const res = await fetch(`${cfg.baseUrl}/api/agent-alive`, {
246
+ method: 'POST',
247
+ headers: { Authorization: `Bearer ${realtimeToken}`, 'Content-Type': 'application/json' },
248
+ body: JSON.stringify({ token: cfg.token }),
249
+ });
250
+ if (!res.ok) console.error(`[agent] Ping failed: ${res.status}`);
251
+ } catch (err) {
252
+ console.error('[agent] Ping error:', err);
253
+ }
254
+ };
255
+ await ping();
256
+ setInterval(ping, 30_000);
257
+ }
258
+
259
+ // ── Ralph loop ────────────────────────────────────────────────────────────────
260
+
261
+ function hasPendingTasks(kitDir) {
262
+ const tasksPath = join(kitDir, 'tasks.json');
263
+ if (!existsSync(tasksPath)) return false;
264
+ try {
265
+ const tasks = JSON.parse(readFileSync(tasksPath, 'utf-8'));
266
+ return Array.isArray(tasks) && tasks.length > 0;
267
+ } catch {
268
+ return false;
269
+ }
270
+ }
271
+
272
+ function readCurrentContext(kitDir) {
273
+ const ctxPath = join(kitDir, '.slices', 'current_context.json');
274
+ if (!existsSync(ctxPath)) return null;
275
+ try { return JSON.parse(readFileSync(ctxPath, 'utf-8')).name || null; } catch { return null; }
276
+ }
277
+
278
+ // Returns the first Planned slice IN THE CURRENT CONTEXT ONLY. If the current
279
+ // context has no planned work, returns null so the loop waits — it must NEVER
280
+ // cross into another context to find something to build.
281
+ function getFirstPlannedSliceTitle(kitDir) {
282
+ const currentCtx = readCurrentContext(kitDir);
283
+ if (!currentCtx) return null;
284
+ const indexPath = join(kitDir, '.slices', currentCtx, 'index.json');
285
+ if (!existsSync(indexPath)) return null;
286
+ try {
287
+ const { slices } = JSON.parse(readFileSync(indexPath, 'utf-8'));
288
+ const planned = slices && slices.find((s) => (s.status || '').toLowerCase() === 'planned');
289
+ if (planned) return planned.slice || planned.id || null;
290
+ } catch {}
291
+ return null;
292
+ }
293
+
294
+ async function runWithRetry(label, fn) {
295
+ while (true) {
296
+ try {
297
+ console.log(`[ralph] ${label}`);
298
+ await fn();
299
+ return;
300
+ } catch (err) {
301
+ console.error(`[ralph] Error — retrying in 60s:`, err.message);
302
+ await new Promise((r) => setTimeout(r, 60_000));
303
+ }
304
+ }
305
+ }
306
+
307
+ async function ralphLoop(kitDir, cfg, onTask, onPlannedSlice) {
308
+ const promptFile = join(kitDir, 'lib', 'prompt.md');
309
+ const backendPromptFile = join(kitDir, 'lib', 'backend-prompt.md');
310
+ const credentialed = hasCredentials(cfg);
311
+ let lastIdleCtx;
312
+
313
+ while (true) {
314
+ let didWork = false;
315
+
316
+ if (credentialed && hasPendingTasks(kitDir)) {
317
+ const prompt = readFileSync(promptFile, 'utf-8');
318
+ await runWithRetry('onTask: loading slice from board...', () => onTask(prompt));
319
+ await fetchAndPersistSlices(cfg, kitDir).catch(() => {});
320
+ didWork = true;
321
+ }
322
+
323
+ const plannedTitle = onPlannedSlice && getFirstPlannedSliceTitle(kitDir);
324
+ if (plannedTitle) {
325
+ const prompt = readFileSync(backendPromptFile, 'utf-8');
326
+ await runWithRetry(`onPlannedSlice: building slice "${plannedTitle}"...`, () => onPlannedSlice(prompt));
327
+ console.log(`[ralph] Slice build complete — waiting for next slice`);
328
+ if (credentialed) await fetchAndPersistSlices(cfg, kitDir).catch(() => {});
329
+ didWork = true;
330
+ }
331
+
332
+ if (!didWork) {
333
+ // No planned work in the current context — wait, do NOT switch contexts.
334
+ const ctx = readCurrentContext(kitDir);
335
+ if (ctx !== lastIdleCtx) {
336
+ console.log(`[ralph] No planned slices in current context "${ctx}" — waiting. Switch context on the board to continue.`);
337
+ lastIdleCtx = ctx;
338
+ }
339
+ await new Promise((r) => setTimeout(r, 10_000));
340
+ } else {
341
+ lastIdleCtx = undefined;
342
+ }
343
+ }
344
+ }
345
+
346
+ // ── Public API ────────────────────────────────────────────────────────────────
347
+
348
+ export { loadLocalConfig, fetchPlatformConfig, retryOn401, startRealtimeAgent };
349
+
350
+ export async function startRalph({ kitDir, projectDir, onTask, onPlannedSlice }) {
351
+ const local = loadLocalConfig(kitDir);
352
+
353
+ console.log(`Ralph — kit: ${kitDir}`);
354
+ console.log(` project: ${projectDir}`);
355
+
356
+ if (!hasCredentials(local)) {
357
+ console.log(` mode: local-only (no platform sync)\n`);
358
+ await ralphLoop(kitDir, local, onTask, onPlannedSlice);
359
+ return;
360
+ }
361
+
362
+ const cfg = await retryOn401('fetchPlatformConfig', () => fetchPlatformConfig(local));
363
+ console.log(` org=${cfg.organizationId}, board=${cfg.boardId}, base=${cfg.baseUrl}\n`);
364
+
365
+ await Promise.all([
366
+ startRealtimeAgent(cfg, kitDir),
367
+ ralphLoop(kitDir, cfg, onTask, onPlannedSlice),
368
+ ]);
369
+ }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ // Ralph loop + realtime agent using Claude Code as the executor.
3
+ // Usage: node ralph-claude.js [project_dir]
4
+
5
+ import { startRalph, loadLocalConfig } from './lib/ralph.js';
6
+ import { spawn } from 'child_process';
7
+ import { dirname, resolve } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ const kitDir = dirname(fileURLToPath(import.meta.url));
11
+ const projectDir = process.argv[2] ? resolve(process.argv[2]) : resolve(kitDir, '..');
12
+
13
+ const cfg = loadLocalConfig(kitDir);
14
+ const inlineHeader = cfg.boardId
15
+ ? `board=${cfg.boardId} token=${cfg.token} org=${cfg.organizationId} baseUrl=${cfg.baseUrl}\n\n`
16
+ : '';
17
+
18
+ const claudeArgs = ['--dangerously-skip-permissions'];
19
+ if (cfg.model) claudeArgs.push('--model', cfg.model);
20
+ const claudeEnv = cfg.anthropicBaseUrl
21
+ ? { ...process.env, ANTHROPIC_BASE_URL: cfg.anthropicBaseUrl }
22
+ : process.env;
23
+
24
+ function runClaude(prompt) {
25
+ return new Promise((resolve, reject) => {
26
+ const proc = spawn('claude', [...claudeArgs, '-p', inlineHeader + prompt], {
27
+ cwd: projectDir,
28
+ stdio: 'inherit',
29
+ env: claudeEnv,
30
+ });
31
+ proc.on('close', (code) => (code === 0 ? resolve() : reject(new Error(`Claude exited ${code}`))));
32
+ proc.on('error', reject);
33
+ });
34
+ }
35
+
36
+ startRalph({
37
+ kitDir,
38
+ projectDir,
39
+ onTask: runClaude,
40
+ onPlannedSlice: runClaude,
41
+ }).catch((err) => {
42
+ console.error('[ralph] Fatal:', err);
43
+ process.exit(1);
44
+ });
@@ -0,0 +1,98 @@
1
+ #!/bin/bash
2
+ # Ralph agent loop — two independent loops, each triggered by their own condition
3
+ #
4
+ # onTask: tasks.json has entries → load slice from board, update .build-kit/.slices/
5
+ # onPlannedSlice: .build-kit/.slices/ has a "Planned" slice → build it
6
+ #
7
+ # The loops are NOT causally linked — either can trigger on its own.
8
+ #
9
+ # Usage: ./ralph.sh [iterations] [project_dir]
10
+ # iterations — number of loop cycles to run; 0 or omitted means run forever
11
+ # project_dir — path to the project root; defaults to the parent of .build-kit
12
+
13
+ set -euo pipefail
14
+
15
+ KIT_DIR="$(cd "$(dirname "$0")" && pwd)"
16
+ ITERATIONS="${1:-0}"
17
+ PROJECT_DIR="${2:-"$KIT_DIR/.."}"
18
+ TASKS_FILE="$KIT_DIR/tasks.json"
19
+ PROMPT_FILE="$KIT_DIR/lib/prompt.md"
20
+ BACKEND_PROMPT_FILE="$KIT_DIR/lib/backend-prompt.md"
21
+ AGENT_SCRIPT="$KIT_DIR/lib/agent.sh"
22
+
23
+ HAS_CREDENTIALS=true
24
+ if [[ ! -f "$KIT_DIR/.eventmodelers/config.json" ]]; then
25
+ echo "[ralph] Note: no .eventmodelers/config.json found — platform sync disabled." >&2
26
+ echo " To enable board sync, follow: https://app.eventmodelers.ai/documentation#build-supabase" >&2
27
+ echo " Code generation from local slice definitions will still run." >&2
28
+ HAS_CREDENTIALS=false
29
+ fi
30
+
31
+ echo "Ralph — kit: $KIT_DIR project: $PROJECT_DIR"
32
+
33
+ # Returns 0 if tasks.json has at least one task
34
+ has_pending_tasks() {
35
+ [[ -f "$TASKS_FILE" ]] || return 1
36
+ local content
37
+ content=$(cat "$TASKS_FILE")
38
+ [[ "$content" != "[]" && -n "$content" ]]
39
+ }
40
+
41
+ # Returns 0 if any JSON under .build-kit/.slices/ contains a "Planned" status
42
+ has_planned_slices() {
43
+ grep -rqi '"status"[[:space:]]*:[[:space:]]*"planned"' "$KIT_DIR/.slices/" --include='index.json' 2>/dev/null
44
+ }
45
+
46
+ # Returns the title of the first "Planned" slice, or empty string
47
+ get_planned_slice_title() {
48
+ for index_file in "$KIT_DIR/.slices/"*/index.json; do
49
+ [[ -f "$index_file" ]] || continue
50
+ local title
51
+ title=$(node -e "
52
+ try {
53
+ const d = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf-8'));
54
+ const s = (d.slices||[]).find(s => (s.status||'').toLowerCase() === 'planned');
55
+ if (s) process.stdout.write(s.slice || s.id || '');
56
+ } catch(e) {}
57
+ " "$index_file" 2>/dev/null)
58
+ if [[ -n "$title" ]]; then
59
+ echo "$title"
60
+ return
61
+ fi
62
+ done
63
+ }
64
+
65
+ # Runs agent.sh with the given prompt; retries on non-zero exit
66
+ run_agent() {
67
+ local label="$1"
68
+ local prompt="$2"
69
+ while true; do
70
+ echo "[$(date -u +%H:%M:%S)] $label"
71
+ (cd "$PROJECT_DIR" && bash "$AGENT_SCRIPT" "$prompt") 2>&1 && return 0
72
+ echo "[$(date -u +%H:%M:%S)] Agent error — retrying in 60s..."
73
+ sleep 60
74
+ done
75
+ }
76
+
77
+ cycle=0
78
+ while [[ "$ITERATIONS" -eq 0 || "$cycle" -lt "$ITERATIONS" ]]; do
79
+ ran_something=false
80
+
81
+ if [[ "$HAS_CREDENTIALS" == true ]] && has_pending_tasks; then
82
+ run_agent "onTask: loading slice from board..." "$(cat "$PROMPT_FILE")"
83
+ ran_something=true
84
+ fi
85
+
86
+ if has_planned_slices; then
87
+ slice_title=$(get_planned_slice_title)
88
+ run_agent "onPlannedSlice: building \"$slice_title\"..." "$(cat "$BACKEND_PROMPT_FILE")"
89
+ echo "[$(date -u +%H:%M:%S)] Slice \"$slice_title\" build complete — waiting for next slice"
90
+ ran_something=true
91
+ fi
92
+
93
+ if [[ "$ran_something" == false ]]; then
94
+ sleep 3
95
+ fi
96
+
97
+ (( cycle++ )) || true
98
+ done
@@ -0,0 +1,22 @@
1
+ # Supabase
2
+ SUPABASE_URL=https://<project-id>.supabase.co
3
+ SUPABASE_PUBLISHABLE_KEY=<supabase-anon-key>
4
+ SUPABASE_SECRET_KEY=<supabase-service-role-key>
5
+ SUPABASE_DB_URL=postgresql://postgres.<project-id>:<db-password>@aws-1-eu-central-1.pooler.supabase.com:5432/postgres?prepareThreshold=0
6
+
7
+ # Server
8
+ PORT=3000
9
+ API_URL=http://localhost:3000
10
+ BACKEND_URL=http://localhost:3000
11
+
12
+ # Flyway (database migrations)
13
+ FLYWAY_URL=jdbc:postgresql://aws-1-eu-west-1.pooler.supabase.com:6543/postgres?user=postgres.<project-id>&password=<db-password>
14
+ FLYWAY_USER=postgres.<project-id>
15
+ FLYWAY_PASSWORD=<db-password>
16
+
17
+ # Optional
18
+ TESTING=false
19
+
20
+ # Mount privileged common routes (e.g. POST /api/replay/:projection).
21
+ # Disabled by default; the replay route additionally requires a sys user.
22
+ COMMON_ROUTES_ENABLED=false
@@ -0,0 +1,61 @@
1
+ # Project Configuration
2
+
3
+ Read Events in src/events to understand the global structure.
4
+
5
+ ## File Structure Constraints
6
+
7
+ - **Strict Path Limitation**: if not instructed otherwise, only check `src/slices/{slicename}/*.ts`
8
+ - **Slice Organization**: Each feature/domain should be organized as a separate slice
9
+
10
+ ## Code Standards
11
+
12
+ - **Language**: TypeScript only
13
+ - **Module System**: Use ES modules (import/export)
14
+ - **Type Safety**: Ensure all code is properly typed
15
+
16
+ ## Development Guidelines
17
+
18
+ 1. Each slice should be self-contained and focused on a specific domain
19
+ 2. Maintain clear separation of concerns within each slice
20
+ 3. Follow TypeScript best practices for type definitions and interfaces
21
+
22
+ Only check src/slices/{slice}/*.ts, do not check subfolders unless explicitely tasked to.
23
+ If not tasked explicitely to change routes, ignore routes*.ts
24
+
25
+ Ignore case for files and slices in prompts. "CartItems" slice is the same as "cartitems"
26
+
27
+ Do not change files with tests unless explicitely instructed: *.test.ts
28
+
29
+ At the start of every session, read `AGENTS.md` if it exists to load accumulated project learnings.
30
+
31
+ When starting to work on a slice, invoke the `update-slice-status` skill with `InProgress` status before doing anything else.
32
+
33
+ ## Building a Slice
34
+
35
+ **CRITICAL: You MUST always use the provided skills to build slices. NEVER implement a slice manually.**
36
+ **ALL fields, event names, command names, and business rules MUST come exclusively from slice.json. Do NOT invent, assume, or guess any field or logic not present in the slice definition.**
37
+
38
+ When asked to build a slice, always follow this flow:
39
+
40
+ 1. Read the slice definition from `.build-kit/.slices/<context>/<slicename>/slice.json`.
41
+ 2. Determine the slice type:
42
+ - **Webhook** — `sliceType === "WEBHOOK"` → invoke `/build-webhook`
43
+ - **Translation** — `sliceType === "TRANSLATION"` → read `description` and `notes` from slice.json; use `/build-webhook` if notes describe an HTTP endpoint, otherwise default to `/build-automation`
44
+ - **Automation** — `processors` array is non-empty → invoke `/build-automation`
45
+ - **State-view** — `projections` or `queries` array is non-empty → invoke `/build-state-view`
46
+ - **State-change** — default (has `commands` / `events`) → invoke `/build-state-change`
47
+ 3. Invoke the matching skill and follow its instructions completely. Do not deviate.
48
+ 4. **Verify against slice.json**: After the skill completes, check that every command field, event field, and specification in slice.json appears in the implementation. No invented fields — if it is not in slice.json, it must not be in the code.
49
+ 5. Run quality checks (`npm run build`, then the slice tests only).
50
+ 6. If checks pass, commit with `feat: [Slice Name]` and set slice status to `Done`.
51
+
52
+ After you are done, automatically run the tests for the slice that was edited.
53
+
54
+ ## Example Slice Structure
55
+
56
+ ```
57
+ src/slices/
58
+ ├── {slice-name}/
59
+ │ ├── CommandHandler.ts
60
+ │ └── routes.ts
61
+ ```
@@ -0,0 +1,17 @@
1
+ # Flyway configuration file
2
+ # Database connection from .env file
3
+ flyway.url=${FLYWAY_URL}
4
+ flyway.user=${FLYWAY_USER}
5
+ flyway.password=${FLYWAY_PASSWORD}
6
+
7
+ # Migration files location
8
+ flyway.locations=filesystem:./supabase/migrations
9
+
10
+ # Default schema (Flyway will create flyway_schema_history table here)
11
+ flyway.schemas=public
12
+
13
+ # Placeholder replacement
14
+ flyway.placeholderReplacement=false
15
+
16
+ # Validate on migrate
17
+ flyway.validateOnMigrate=true
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "project",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "flyway:migrate": "dotenv -e .env -- flyway -baselineOnMigrate=true migrate",
7
+ "dev": "node --env-file=.env --require ts-node/register server.ts",
8
+ "build": "tsc",
9
+ "start": "NODE_ENV=production node --env-file=.env --require ts-node/register server.ts",
10
+ "test": "tsx --test 'src/**/*.test.ts'"
11
+ },
12
+ "dependencies": {
13
+ "@event-driven-io/emmett": "^0.42.1-alpha.1",
14
+ "@event-driven-io/emmett-expressjs": "^0.42.1-alpha.1",
15
+ "@event-driven-io/emmett-postgresql": "^0.42.1-alpha.1",
16
+ "@modelcontextprotocol/sdk": "^1.29.0",
17
+ "@resvg/resvg-js": "^2.6.2",
18
+ "@supabase/ssr": "^0.10.0",
19
+ "@supabase/supabase-js": "^2.100.1",
20
+ "cookie-parser": "^1.4.7",
21
+ "cors": "^2.8.6",
22
+ "express": "^4.18.2",
23
+ "glob": "^11.0.3",
24
+ "isomorphic-git": "^1.37.6",
25
+ "jose": "^6.2.3",
26
+ "knex": "^3.1.0",
27
+ "multer": "^2.1.1",
28
+ "node-cron": "^4.2.1",
29
+ "pg": "^8.17.2",
30
+ "sharp": "^0.34.5",
31
+ "simple-git": "^3.36.0",
32
+ "swagger-jsdoc": "^6.2.8",
33
+ "swagger-ui-express": "^5.0.1",
34
+ "url": "^0.11.4"
35
+ },
36
+ "devDependencies": {
37
+ "@eslint/eslintrc": "^3",
38
+ "@testcontainers/postgresql": "^11.0.3",
39
+ "@types/cors": "^2.8.19",
40
+ "@types/express": "^4.17.21",
41
+ "@types/multer": "^2.1.0",
42
+ "@types/node": "^20",
43
+ "@types/swagger-jsdoc": "^6.0.4",
44
+ "@types/swagger-ui-express": "^4.1.8",
45
+ "dotenv-cli": "^11.0.0",
46
+ "eslint": "^9",
47
+ "sql-formatter": "^15.7.0",
48
+ "ts-node": "^10.9.2",
49
+ "tsx": "^4.20.3",
50
+ "typescript": "^5"
51
+ }
52
+ }