@nebulit/embuilder 0.1.39

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 (212) hide show
  1. package/README.md +254 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +138 -0
  4. package/package.json +49 -0
  5. package/templates/.claude/hooks/QUICKSTART.md +256 -0
  6. package/templates/.claude/hooks/README.md +533 -0
  7. package/templates/.claude/hooks/analyze-commit.sh +22 -0
  8. package/templates/.claude/hooks/analyze-commit.ts +518 -0
  9. package/templates/.claude/hooks/analyzers/README.md +198 -0
  10. package/templates/.claude/hooks/analyzers/code-quality-checker.ts +154 -0
  11. package/templates/.claude/hooks/analyzers/code-quality.md +54 -0
  12. package/templates/.claude/hooks/analyzers/commit-blocker-example.ts.disabled +110 -0
  13. package/templates/.claude/hooks/analyzers/commit-policy.md +49 -0
  14. package/templates/.claude/hooks/analyzers/event-model-validator.md +49 -0
  15. package/templates/.claude/hooks/analyzers/event-model-validator.ts +169 -0
  16. package/templates/.claude/hooks/analyzers/example-logger.ts +70 -0
  17. package/templates/.claude/hooks/analyzers/slice-scope-validator.md +81 -0
  18. package/templates/.claude/hooks/check-review-result.sh +47 -0
  19. package/templates/.claude/hooks/prepare-review.sh +34 -0
  20. package/templates/.claude/hooks/review-agent-prompt.md +42 -0
  21. package/templates/.claude/hooks/run-review-agent.sh +124 -0
  22. package/templates/.claude/settings.local.json +37 -0
  23. package/templates/.claude/skills/help/README.md +84 -0
  24. package/templates/.claude/skills/help/SKILL.md +393 -0
  25. package/templates/.claude/skills/help/templates/demo-config.json +6753 -0
  26. package/templates/.claude/skills/sample-slices/SKILL.md +8 -0
  27. package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/code-slice.json +124 -0
  28. package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/slice.json +255 -0
  29. package/templates/.claude/skills/sample-slices/templates/.slices/Library/availablebooks/slice.json +107 -0
  30. package/templates/.claude/skills/sample-slices/templates/.slices/index.json +20 -0
  31. package/templates/.claude/skills/sample-slices/templates/Cart/additem/slice.json +979 -0
  32. package/templates/.claude/skills/sample-slices/templates/Cart/archiveitem/slice.json +529 -0
  33. package/templates/.claude/skills/sample-slices/templates/Cart/cartitems/slice.json +1072 -0
  34. package/templates/.claude/skills/sample-slices/templates/Cart/cartwithproducts/slice.json +394 -0
  35. package/templates/.claude/skills/sample-slices/templates/Cart/changedprices/slice.json +88 -0
  36. package/templates/.claude/skills/sample-slices/templates/Cart/changeinventory/slice.json +264 -0
  37. package/templates/.claude/skills/sample-slices/templates/Cart/changeprice/slice.json +308 -0
  38. package/templates/.claude/skills/sample-slices/templates/Cart/clearcart/slice.json +358 -0
  39. package/templates/.claude/skills/sample-slices/templates/Cart/inventories/slice.json +203 -0
  40. package/templates/.claude/skills/sample-slices/templates/Cart/publishcart/slice.json +876 -0
  41. package/templates/.claude/skills/sample-slices/templates/Cart/removeitem/slice.json +560 -0
  42. package/templates/.claude/skills/sample-slices/templates/Cart/submitcart/slice.json +708 -0
  43. package/templates/.claude/skills/sample-slices/templates/Cart/submittedcartdata/slice.json +399 -0
  44. package/templates/.claude/skills/sample-slices/templates/index.json +108 -0
  45. package/templates/.claude/skills/slice-automation/SKILL.md +49 -0
  46. package/templates/.claude/skills/slice-state-change/SKILL.md +369 -0
  47. package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocation.test.ts.sample +76 -0
  48. package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocationCommand.ts.sample +84 -0
  49. package/templates/.claude/skills/slice-state-change/templates/AddLocation/routes.ts.sample +73 -0
  50. package/templates/.claude/skills/slice-state-change/templates/README.md +46 -0
  51. package/templates/.claude/skills/slice-state-view/SKILL.md +336 -0
  52. package/templates/.claude/skills/slice-state-view/templates/Locations/Locations.test.ts.sample +84 -0
  53. package/templates/.claude/skills/slice-state-view/templates/Locations/LocationsProjection.ts.sample +50 -0
  54. package/templates/.claude/skills/slice-state-view/templates/Locations/routes.ts.sample +46 -0
  55. package/templates/.claude/skills/slice-state-view/templates/README.md +109 -0
  56. package/templates/.claude/skills/slice-state-view/templates/Tables/Tables.test.ts.sample +104 -0
  57. package/templates/.claude/skills/slice-state-view/templates/Tables/TablesProjection.ts.sample +59 -0
  58. package/templates/.claude/skills/slice-state-view/templates/Tables/routes.ts.sample +46 -0
  59. package/templates/.claude/skills/slice-state-view/templates/V2__tables.sql +7 -0
  60. package/templates/.claude/skills/slice-state-view/templates/V8__locations.sql +7 -0
  61. package/templates/.claude/skills/test-analyzer/SKILL.md +373 -0
  62. package/templates/.claude/skills/test-analyzer/examples/specification-format.md +143 -0
  63. package/templates/.claude/skills/test-analyzer/examples/state-change-example.md +111 -0
  64. package/templates/.claude/skills/test-analyzer/examples/state-view-example.md +122 -0
  65. package/templates/AGENTS.md +110 -0
  66. package/templates/Claude.md +58 -0
  67. package/templates/README.md +178 -0
  68. package/templates/backend/.env +9 -0
  69. package/templates/backend/BACKEND_AUTH_SETUP.md +183 -0
  70. package/templates/backend/SWAGGER.md +213 -0
  71. package/templates/backend/eslint.config.mjs +31 -0
  72. package/templates/backend/flyway.conf +17 -0
  73. package/templates/backend/package.json +44 -0
  74. package/templates/backend/prd.json.example +64 -0
  75. package/templates/backend/public/assets/images/banner.png +0 -0
  76. package/templates/backend/public/assets/logo.png +0 -0
  77. package/templates/backend/public/file.svg +4 -0
  78. package/templates/backend/public/globe.svg +12 -0
  79. package/templates/backend/public/next.svg +6 -0
  80. package/templates/backend/public/vercel.svg +3 -0
  81. package/templates/backend/public/window.svg +5 -0
  82. package/templates/backend/server.ts +129 -0
  83. package/templates/backend/setup-env.sh +50 -0
  84. package/templates/backend/src/common/assertions.ts +6 -0
  85. package/templates/backend/src/common/db.ts +1 -0
  86. package/templates/backend/src/common/loadPostgresEventstore.ts +16 -0
  87. package/templates/backend/src/common/parseEndpoint.ts +51 -0
  88. package/templates/backend/src/common/replay.ts +9 -0
  89. package/templates/backend/src/common/routes.ts +19 -0
  90. package/templates/backend/src/common/testHelpers.ts +53 -0
  91. package/templates/backend/src/core/readmodel.ts +28 -0
  92. package/templates/backend/src/core/types.ts +26 -0
  93. package/templates/backend/src/process/process.ts +53 -0
  94. package/templates/backend/src/supabase/LoginHandler.ts +36 -0
  95. package/templates/backend/src/supabase/ProtectedPageProps.ts +21 -0
  96. package/templates/backend/src/supabase/README.md +171 -0
  97. package/templates/backend/src/supabase/api.ts +63 -0
  98. package/templates/backend/src/supabase/authMiddleware.ts +53 -0
  99. package/templates/backend/src/supabase/component.ts +12 -0
  100. package/templates/backend/src/supabase/requireUser.ts +72 -0
  101. package/templates/backend/src/supabase/serverProps.ts +25 -0
  102. package/templates/backend/src/supabase/staticProps.ts +10 -0
  103. package/templates/backend/src/swagger.ts +34 -0
  104. package/templates/backend/src/util/assertions.ts +6 -0
  105. package/templates/backend/supabase/config.toml +295 -0
  106. package/templates/backend/supabase/migrations/20260121155918593_catalogentries.sql.sample +23 -0
  107. package/templates/backend/supabase/seed.sql +1 -0
  108. package/templates/backend/tsconfig.json +31 -0
  109. package/templates/frontend/.env.development +3 -0
  110. package/templates/frontend/AGENTS.md +7 -0
  111. package/templates/frontend/README.md +73 -0
  112. package/templates/frontend/components.json +20 -0
  113. package/templates/frontend/eslint.config.js +26 -0
  114. package/templates/frontend/index.html +18 -0
  115. package/templates/frontend/package-lock.json +8347 -0
  116. package/templates/frontend/package.json +94 -0
  117. package/templates/frontend/postcss.config.js +6 -0
  118. package/templates/frontend/public/favicon.ico +0 -0
  119. package/templates/frontend/public/logo.png +0 -0
  120. package/templates/frontend/public/placeholder.svg +1 -0
  121. package/templates/frontend/public/robots.txt +14 -0
  122. package/templates/frontend/src/App.css +42 -0
  123. package/templates/frontend/src/App.tsx +47 -0
  124. package/templates/frontend/src/components/NavLink.tsx +28 -0
  125. package/templates/frontend/src/components/ProtectedRoute.tsx +24 -0
  126. package/templates/frontend/src/components/calendar/Calendar.tsx +302 -0
  127. package/templates/frontend/src/components/layout/DashboardLayout.tsx +21 -0
  128. package/templates/frontend/src/components/layout/Header.tsx +45 -0
  129. package/templates/frontend/src/components/layout/Sidebar.tsx +82 -0
  130. package/templates/frontend/src/components/tables/ReservationTemplates.tsx +189 -0
  131. package/templates/frontend/src/components/ui/accordion.tsx +52 -0
  132. package/templates/frontend/src/components/ui/alert-dialog.tsx +104 -0
  133. package/templates/frontend/src/components/ui/alert.tsx +43 -0
  134. package/templates/frontend/src/components/ui/aspect-ratio.tsx +5 -0
  135. package/templates/frontend/src/components/ui/avatar.tsx +38 -0
  136. package/templates/frontend/src/components/ui/badge.tsx +29 -0
  137. package/templates/frontend/src/components/ui/breadcrumb.tsx +90 -0
  138. package/templates/frontend/src/components/ui/button.tsx +47 -0
  139. package/templates/frontend/src/components/ui/calendar.tsx +54 -0
  140. package/templates/frontend/src/components/ui/card.tsx +43 -0
  141. package/templates/frontend/src/components/ui/carousel.tsx +224 -0
  142. package/templates/frontend/src/components/ui/chart.tsx +303 -0
  143. package/templates/frontend/src/components/ui/checkbox.tsx +26 -0
  144. package/templates/frontend/src/components/ui/collapsible.tsx +9 -0
  145. package/templates/frontend/src/components/ui/command.tsx +132 -0
  146. package/templates/frontend/src/components/ui/context-menu.tsx +178 -0
  147. package/templates/frontend/src/components/ui/dialog.tsx +95 -0
  148. package/templates/frontend/src/components/ui/drawer.tsx +87 -0
  149. package/templates/frontend/src/components/ui/dropdown-menu.tsx +179 -0
  150. package/templates/frontend/src/components/ui/form.tsx +129 -0
  151. package/templates/frontend/src/components/ui/hover-card.tsx +27 -0
  152. package/templates/frontend/src/components/ui/input-otp.tsx +61 -0
  153. package/templates/frontend/src/components/ui/input.tsx +22 -0
  154. package/templates/frontend/src/components/ui/label.tsx +17 -0
  155. package/templates/frontend/src/components/ui/menubar.tsx +207 -0
  156. package/templates/frontend/src/components/ui/navigation-menu.tsx +120 -0
  157. package/templates/frontend/src/components/ui/pagination.tsx +81 -0
  158. package/templates/frontend/src/components/ui/popover.tsx +29 -0
  159. package/templates/frontend/src/components/ui/progress.tsx +23 -0
  160. package/templates/frontend/src/components/ui/radio-group.tsx +36 -0
  161. package/templates/frontend/src/components/ui/resizable.tsx +37 -0
  162. package/templates/frontend/src/components/ui/scroll-area.tsx +38 -0
  163. package/templates/frontend/src/components/ui/select.tsx +143 -0
  164. package/templates/frontend/src/components/ui/separator.tsx +20 -0
  165. package/templates/frontend/src/components/ui/sheet.tsx +107 -0
  166. package/templates/frontend/src/components/ui/sidebar.tsx +637 -0
  167. package/templates/frontend/src/components/ui/skeleton.tsx +7 -0
  168. package/templates/frontend/src/components/ui/slider.tsx +23 -0
  169. package/templates/frontend/src/components/ui/sonner.tsx +27 -0
  170. package/templates/frontend/src/components/ui/stat-card.tsx +44 -0
  171. package/templates/frontend/src/components/ui/switch.tsx +27 -0
  172. package/templates/frontend/src/components/ui/table.tsx +72 -0
  173. package/templates/frontend/src/components/ui/tabs.tsx +53 -0
  174. package/templates/frontend/src/components/ui/textarea.tsx +21 -0
  175. package/templates/frontend/src/components/ui/toast.tsx +111 -0
  176. package/templates/frontend/src/components/ui/toaster.tsx +24 -0
  177. package/templates/frontend/src/components/ui/toggle-group.tsx +49 -0
  178. package/templates/frontend/src/components/ui/toggle.tsx +37 -0
  179. package/templates/frontend/src/components/ui/tooltip.tsx +28 -0
  180. package/templates/frontend/src/components/ui/use-toast.ts +3 -0
  181. package/templates/frontend/src/contexts/AuthContext.tsx +94 -0
  182. package/templates/frontend/src/contexts/RefreshContext.tsx +236 -0
  183. package/templates/frontend/src/hooks/api/index.ts +2 -0
  184. package/templates/frontend/src/hooks/api/useLocations.ts +15 -0
  185. package/templates/frontend/src/hooks/use-mobile.tsx +19 -0
  186. package/templates/frontend/src/hooks/use-toast.ts +186 -0
  187. package/templates/frontend/src/hooks/useApiContext.ts +11 -0
  188. package/templates/frontend/src/index.css +118 -0
  189. package/templates/frontend/src/integrations/supabase/client.ts +9 -0
  190. package/templates/frontend/src/lib/api-client.ts +136 -0
  191. package/templates/frontend/src/lib/api.ts +1028 -0
  192. package/templates/frontend/src/lib/utils.ts +6 -0
  193. package/templates/frontend/src/main.tsx +5 -0
  194. package/templates/frontend/src/pages/Auth.tsx +408 -0
  195. package/templates/frontend/src/pages/Dashboard.tsx +168 -0
  196. package/templates/frontend/src/pages/Menus.tsx +224 -0
  197. package/templates/frontend/src/pages/NotFound.tsx +24 -0
  198. package/templates/frontend/src/pages/Register.tsx +285 -0
  199. package/templates/frontend/src/test/example.test.ts +0 -0
  200. package/templates/frontend/src/test/setup.ts +15 -0
  201. package/templates/frontend/src/types/index.ts +8 -0
  202. package/templates/frontend/src/vite-env.d.ts +1 -0
  203. package/templates/frontend/tailwind.config.ts +101 -0
  204. package/templates/frontend/tsconfig.app.json +31 -0
  205. package/templates/frontend/tsconfig.json +16 -0
  206. package/templates/frontend/tsconfig.node.json +22 -0
  207. package/templates/frontend/vite.config.ts +21 -0
  208. package/templates/frontend/vitest.config.ts +16 -0
  209. package/templates/init.sh +1 -0
  210. package/templates/prompt.md +139 -0
  211. package/templates/ralph.sh +120 -0
  212. package/templates/server.mjs +505 -0
@@ -0,0 +1,104 @@
1
+ import {before, after, describe, it} from "node:test";
2
+ import {PostgreSQLProjectionAssert, PostgreSQLProjectionSpec} from "@event-driven-io/emmett-postgresql";
3
+ import {TablesProjection} from "./TablesProjection";
4
+ import {PostgreSqlContainer, StartedPostgreSqlContainer} from "@testcontainers/postgresql";
5
+ import {TableAdded} from "../../events/TableAdded"
6
+ import {TableUpdated} from "../../events/TableUpdated"
7
+ import knex from 'knex';
8
+ import assert from 'assert';
9
+ import {runFlywayMigrations} from "../../common/testHelpers";
10
+
11
+ describe('Tables Specification', () => {
12
+ let postgres: StartedPostgreSqlContainer;
13
+ let connectionString: string
14
+
15
+ let given: PostgreSQLProjectionSpec<TableAdded | TableUpdated>
16
+
17
+ before(async () => {
18
+ postgres = await new PostgreSqlContainer("postgres").start();
19
+ connectionString = postgres.getConnectionUri();
20
+
21
+ await runFlywayMigrations(connectionString)
22
+
23
+ given = PostgreSQLProjectionSpec.for({
24
+ projection: TablesProjection,
25
+ connectionString,
26
+ });
27
+ });
28
+
29
+ after(async () => {
30
+ await postgres?.stop();
31
+ });
32
+
33
+ it('spec: Tables - add and update scenario', async () => {
34
+ const tableId1 = "fbf165d3-5fe2-4a34-af24-f0ad64ca8412"
35
+ const tableId2 = "3ccaf75c-7120-4e4d-9de9-c5ad822a62ec"
36
+
37
+ const assertTables: PostgreSQLProjectionAssert = async ({connectionString: connStr}) => {
38
+ const queryDb = knex({
39
+ client: 'pg',
40
+ connection: connStr,
41
+ });
42
+
43
+ try {
44
+ const result = await queryDb('tables')
45
+ .withSchema('public')
46
+ .select('table_id', 'name', 'seats')
47
+ .orderBy('table_id');
48
+
49
+ assert.strictEqual(result.length, 2, 'Should have 2 tables');
50
+
51
+ const table1 = result.find(t => t.table_id === tableId1);
52
+ const table2 = result.find(t => t.table_id === tableId2);
53
+
54
+ assert(table1, 'Table 1 should exist');
55
+ assert.strictEqual(table1.name, "Table 1 Updated");
56
+ assert.strictEqual(table1.seats, 8);
57
+
58
+ assert(table2, 'Table 2 should exist');
59
+ assert.strictEqual(table2.name, "Table 2");
60
+ assert.strictEqual(table2.seats, 6);
61
+ } finally {
62
+ await queryDb.destroy();
63
+ }
64
+ };
65
+
66
+ await given([{
67
+ type: 'TableAdded',
68
+ data: {
69
+ minPersons: 2,
70
+ name: "Table 1",
71
+ reservable: true,
72
+ seats: 4,
73
+ tableid: tableId1
74
+ },
75
+ metadata: {streamName: 'fbf165d3-5fe2-4a34-af24-f0ad64ca8412'}
76
+ }])
77
+ .when([
78
+ {
79
+ type: 'TableAdded',
80
+ data: {
81
+ minPersons: 2,
82
+ name: "Table 2",
83
+ reservable: true,
84
+ seats: 6,
85
+ tableid: tableId2
86
+ },
87
+ metadata: {streamName: 'fbf165d3-5fe2-4a34-af24-f0ad64ca8412'}
88
+ },
89
+ {
90
+ type: 'TableUpdated',
91
+ data: {
92
+ minPersons: 2,
93
+ name: "Table 1 Updated",
94
+ reservable: true,
95
+ seats: 8,
96
+ tableid: tableId1
97
+ },
98
+ metadata: {streamName: 'fbf165d3-5fe2-4a34-af24-f0ad64ca8412'}
99
+ }
100
+ ])
101
+ .then(assertTables);
102
+ });
103
+
104
+ });
@@ -0,0 +1,59 @@
1
+ import {postgreSQLRawSQLProjection} from '@event-driven-io/emmett-postgresql';
2
+ import {sql} from '@event-driven-io/dumbo';
3
+ import knex, {Knex} from 'knex';
4
+ import {ContextEvents} from '../../events/ContextEvents';
5
+
6
+ export type TablesReadModelItem = {
7
+ seats: number,
8
+ name: string,
9
+ tableId: string,
10
+ }
11
+
12
+ export type TablesReadModel = {
13
+ data: TablesReadModelItem[],
14
+ }
15
+
16
+ export const tableName = 'tables';
17
+
18
+ export const getKnexInstance = (connectionString: string): Knex => {
19
+ return knex({
20
+ client: 'pg',
21
+ connection: connectionString,
22
+ });
23
+ };
24
+
25
+
26
+ export const TablesProjection = postgreSQLRawSQLProjection<ContextEvents>({
27
+ canHandle: ['TableAdded', 'TableUpdated'],
28
+ evolve: (event, context) => {
29
+ const {type, data} = event;
30
+ const db = getKnexInstance(context.connection.connectionString);
31
+
32
+ switch (type) {
33
+ case 'TableAdded':
34
+ return sql(db(tableName)
35
+ .withSchema('public')
36
+ .insert({
37
+ table_id: data.tableid,
38
+ name: data.name,
39
+ seats: data.seats,
40
+ })
41
+ .onConflict('table_id')
42
+ .merge({name: data.name, seats: data.seats})
43
+ .toQuery());
44
+
45
+ case 'TableUpdated':
46
+ return sql(db(tableName)
47
+ .withSchema('public')
48
+ .where('table_id', data.tableid)
49
+ .update({
50
+ name: data.name,
51
+ seats: data.seats,
52
+ })
53
+ .toQuery());
54
+
55
+ default:
56
+ return [];
57
+ }
58
+ }
59
+ });
@@ -0,0 +1,46 @@
1
+ import {Request, Response, Router} from 'express';
2
+ import {TablesReadModel, tableName} from "./TablesProjection";
3
+ import {WebApiSetup} from "@event-driven-io/emmett-expressjs";
4
+ import createClient from "../../supabase/api";
5
+ import {readmodel} from "../../core/readmodel";
6
+ import {requireUser} from "../../supabase/requireUser";
7
+
8
+ export const api =
9
+ (
10
+ // external dependencies
11
+ ): WebApiSetup =>
12
+ (router: Router): void => {
13
+ router.get('/api/query/tables-collection', async (req: Request, res: Response) => {
14
+ try {
15
+ const principal = await requireUser(req, res, true);
16
+ if (principal.error) {
17
+ return;
18
+ }
19
+
20
+ const userId = principal.user.id;
21
+ const id = req.query._id?.toString();
22
+
23
+ const supabase = createClient()
24
+
25
+ const query: any = {...req.query, user_id: userId};
26
+ delete query._id;
27
+
28
+ const data: TablesReadModel | TablesReadModel[] | null =
29
+ id ? await readmodel(tableName, supabase).findById<TablesReadModel>("table_id", id) :
30
+ await readmodel(tableName, supabase).findAll<TablesReadModel>(query)
31
+
32
+ // Serialize, handling bigint properly
33
+ const sanitized = JSON.parse(
34
+ JSON.stringify(data || [], (key, value) =>
35
+ typeof value === 'bigint' ? value.toString() : value
36
+ )
37
+ );
38
+
39
+ return res.status(200).json(sanitized);
40
+ } catch (err) {
41
+ console.error(err);
42
+ return res.status(500).json({ok: false, error: 'Server error'});
43
+ }
44
+ });
45
+
46
+ };
@@ -0,0 +1,7 @@
1
+ -- Create tables table
2
+ CREATE TABLE IF NOT EXISTS "public"."tables" (
3
+ restaurant_id uuid,
4
+ table_id TEXT PRIMARY KEY,
5
+ name TEXT,
6
+ seats INTEGER
7
+ );
@@ -0,0 +1,7 @@
1
+ -- Create locations table
2
+ CREATE TABLE IF NOT EXISTS "public"."locations" (
3
+ restaurant_id uuid,
4
+ name TEXT,
5
+ zip_code TEXT,
6
+ city TEXT
7
+ );
@@ -0,0 +1,373 @@
1
+ ---
2
+ name: test-analyzer
3
+ description: analyzes test files and generates slice.json specifications for drift detection
4
+ ---
5
+
6
+ ## Overview
7
+
8
+ Analyzes test files (`*.test.ts`) to generate `code-slice.json` specification files. These capture behavioral contracts in given/when/then form, enabling drift detection between tests and `slice.json` design documentation.
9
+
10
+ **CRITICAL:** This skill NEVER touches existing `slice.json` files. It only creates/deletes `code-slice.json` files.
11
+
12
+ ## Purpose
13
+
14
+ - Extract test specifications from existing test code
15
+ - Generate `code-slice.json` files matching the format of `slice.json` in `.slices/` directory
16
+ - Enable drift detection by comparing `code-slice.json` (what tests say) with `slice.json` (design documentation)
17
+ - **NEVER modify or delete existing `slice.json` files** - only work with `code-slice.json`
18
+
19
+ ## Input
20
+
21
+ - Path to a test file (e.g., `ActivateShift.test.ts` or `OnlineReservationStatus.test.ts`)
22
+ - Test file must follow given/when/then pattern using:
23
+ - `DeciderSpecification.for()` for state-change slices
24
+ - `PostgreSQLProjectionSpec.for()` for state-view slices
25
+
26
+ ## Output Format
27
+
28
+ **File:** `.slices/<Context>/<folder>/code-slice.json` (same directory as `slice.json`)
29
+
30
+ Contains:
31
+ ```json
32
+ {
33
+ "id": "<slice-id-from-slice.json>",
34
+ "title": "<slice-title-from-slice.json>",
35
+ "specifications": [...]
36
+ }
37
+ ```
38
+
39
+ - MUST include `id` and `title` fields copied from the corresponding `slice.json` file
40
+ - NO commands, events, readmodels, or other slice metadata (only id, title, and specifications)
41
+ - Each specification has complete nested objects with fields, NOT simple strings
42
+ - See [specification-format.md](examples/specification-format.md) for detailed structure
43
+
44
+ ### Specifications Array Content
45
+
46
+ Include ALL test cases that are:
47
+ - NOT already in the `specifications` array of existing `slice.json`
48
+ - All given/when/then scenarios from tests (happy path, edge cases, error scenarios)
49
+ - Behavioral contracts being tested
50
+ - NO implementation details, only specifications
51
+
52
+ **IMPORTANT:** Even if a command/event is defined in slice.json's commands/events arrays, if the test specification is NOT in the specifications array, it MUST be included in code-slice.json
53
+
54
+ ### Specification Structure
55
+
56
+ Each specification includes:
57
+ - `vertical`: false
58
+ - `id`: unique generated ID
59
+ - `sliceName`: slice title
60
+ - `type`: COMMAND, EVENT, READMODEL or SPEC_ERROR'
61
+ - `title`: spec description from test
62
+ - `given`: array of event objects (preconditions)
63
+ - STATE_CHANGE: events that happened before
64
+ - STATE_VIEW: events to be projected
65
+ - id is the same as the original elements id
66
+ - `when`: command object OR empty array
67
+ - STATE_CHANGE: single command object (the action being tested)
68
+ - STATE_VIEW: empty array `[]` (events are in `given` for projections)
69
+ - id is the same as the original elements id
70
+ - `then`: array of event/error/readmodel objects
71
+ - Can be events (what happened as a result, type SPEC_EVENT)
72
+ - Can be errors (SPEC_ERROR type)
73
+ - Can be readmodel assertions (SPEC_READMODEL type for projections)
74
+ - id is the same as the original elements id
75
+ - `comments`: empty array
76
+ - `linkedId`: MUST be the same as the specification's `id` field (self-reference)
77
+
78
+ ## Supported Slice Types
79
+
80
+ ### 1. STATE_CHANGE Slices
81
+
82
+ Uses `DeciderSpecification.for()` with decide/evolve pattern.
83
+
84
+ ```typescript
85
+ const given = DeciderSpecification.for({
86
+ decide,
87
+ evolve,
88
+ initialState: ...
89
+ });
90
+
91
+ it('spec: description', () => {
92
+ given([...events])
93
+ .when(command)
94
+ .then([...events])
95
+ });
96
+ ```
97
+
98
+ **See:** [state-change-example.md](examples/state-change-example.md)
99
+
100
+ ### 2. STATE_VIEW Slices
101
+
102
+ Uses `PostgreSQLProjectionSpec.for()` for projections/read models.
103
+
104
+ ```typescript
105
+ given = PostgreSQLProjectionSpec.for({
106
+ projection: SomeProjection,
107
+ connectionString,
108
+ });
109
+
110
+ it('spec: description', () => {
111
+ await given([...events]) // Events are in given() for projections
112
+ .when([]) // when() is always empty for projections
113
+ .then(assertReadModel);
114
+ });
115
+ ```
116
+
117
+ **IMPORTANT for STATE_VIEW:**
118
+ - Events are placed in `given` array (the events to project)
119
+ - `when` is always empty array (no command in projections)
120
+ - `then` contains readmodel assertions
121
+
122
+ **See:** [state-view-example.md](examples/state-view-example.md)
123
+
124
+ ## Analysis Process
125
+
126
+ ### Step 1: Identify Slice Type
127
+
128
+ 1. Read the test file
129
+ 2. Look for `DeciderSpecification.for()` → STATE_CHANGE
130
+ 3. Look for `PostgreSQLProjectionSpec.for()` → STATE_VIEW
131
+ 4. Extract slice name from describe block or file name
132
+
133
+ ### Step 2: Extract Test Specifications
134
+
135
+ For each `it('spec: ...', ...)` test:
136
+
137
+ 1. Extract specification description from test name
138
+ 2. Identify given events (preconditions)
139
+ 3. Identify when command/events (action)
140
+ 4. Identify then events/assertions (postconditions)
141
+ 5. Map to given/when/then structure
142
+ 6. **Compare with existing slice.json:**
143
+ - Check if specification already exists in `slice.json` specifications array
144
+ - If exact match found in specifications array → SKIP
145
+ - If NOT in specifications array → INCLUDE (even if command/event element exists)
146
+
147
+ ### Step 3: Extract Element Definitions
148
+
149
+ #### For STATE_CHANGE:
150
+
151
+ - **Commands:** Extract from `when()` calls with fields and types
152
+ - **Events:** Extract from `given()` and `then()` calls with fields and types
153
+ - **Dependencies:** Commands → Events (OUTBOUND), Events → Commands (INBOUND)
154
+
155
+ #### For STATE_VIEW:
156
+
157
+ - **Events:** Extract from `given()` calls (events to be projected)
158
+ - **IMPORTANT:** `when()` is always empty for STATE_VIEW slices
159
+ - **Read Model:** Extract from projection name and assertion logic in `then()`
160
+ - **Dependencies:** Events → Read Model (OUTBOUND)
161
+
162
+ ### Step 4: Build Specifications Array
163
+
164
+ - Include ALL test cases NOT in `slice.json` specifications array
165
+ - This includes happy path scenarios, error scenarios, edge cases, and alternative flows
166
+ - Format as given/when/then specifications with full field definitions
167
+
168
+ See [specification-format.md](examples/specification-format.md) for complete format details.
169
+
170
+ ## Slice Name and Context Detection
171
+
172
+ **Slice Name:**
173
+ - Extract from test file name: `<SliceName>.test.ts` → `<SliceName>`
174
+ - Example: `ActivateShift.test.ts` → "ActivateShift"
175
+
176
+ **Context:**
177
+ - If metadata contains `restaurantId` or `locationId` → "Restaurant Management"
178
+ - Otherwise → extract from aggregate name or default to "General"
179
+
180
+ **Using `.slices/index.json` for lookup:**
181
+ 1. Read `.slices/index.json` to find correct folder
182
+ 2. Match on slice title (case-insensitive)
183
+ 3. Extract `folder` and `context` from matching entry
184
+ 4. Construct path: `.slices/<context>/<folder>/slice.json`
185
+
186
+ ## Implementation Steps
187
+
188
+ When invoked with a test file path:
189
+
190
+ 1. **Read and parse the test file**
191
+ - Use Read tool to load file
192
+ - Identify testing framework pattern
193
+
194
+ 2. **Extract slice metadata**
195
+ - Parse describe block for slice name
196
+ - Determine slice type (DeciderSpecification = STATE_CHANGE, PostgreSQLProjectionSpec = STATE_VIEW)
197
+ - Extract context from metadata fields
198
+ - Infer slice name from file path
199
+
200
+ 3. **Check for existing files**
201
+ - Lookup `slice.json` location via `.slices/index.json`
202
+ - Read `slice.json` if found for comparison
203
+ - Extract `id` and `title` fields from `slice.json` (required for code-slice.json)
204
+ - Track which elements already exist in design documentation
205
+
206
+ 4. **Analyze test cases**
207
+ - Find all `it('spec: ...')` test cases
208
+ - Extract given/when/then structures
209
+ - Compare with existing `slice.json`
210
+
211
+ 5. **Build specifications array**
212
+ - Include ALL test specifications NOT already in `slice.json` specifications array
213
+ - Format with complete field structures (see [specification-format.md](examples/specification-format.md))
214
+ - **CRITICAL: Look up real IDs from slice.json for linkedId fields:**
215
+ - For each command in `when`: Find matching command in `slice.json` → `commands` array by title
216
+ - For each event in `given`/`then`:
217
+ - First try `slice.json` → `events` array by title
218
+ - For STATE_VIEW: If not found, search in `readmodels` → `dependencies` array (INBOUND events)
219
+ - Use the `id` field from the matched element/dependency as the `linkedId`
220
+ - For each readmodel assertion in `then`: Find matching readmodel in `slice.json` → `readmodels` array by title
221
+ - If no match found, use `null` (indicates new element not in design documentation)
222
+ - For errors (SPEC_ERROR): Do NOT include `linkedId` field (errors don't exist in slice.json)
223
+
224
+ 6. **Decision: Generate, Update, or Remove code-slice.json**
225
+ - **Compare test specifications with `slice.json` specifications array**
226
+ - **All test specs in `slice.json` specifications:** Remove/don't create `code-slice.json` (no drift)
227
+ - **Test specs NOT in `slice.json` specifications:** Generate/update `code-slice.json` with:
228
+ - Write to `.slices/<Context>/<folder>/code-slice.json` (same directory as `slice.json`)
229
+ - `id` field copied from `slice.json`
230
+ - `title` field copied from `slice.json`
231
+ - `specifications` array with missing specifications
232
+ - **NEVER touch `slice.json`** - it's read-only design documentation
233
+
234
+ 7. **Validate output**
235
+ - Ensure JSON is valid
236
+ - Verify all required fields present
237
+ - Check structure matches reference format
238
+
239
+ ## Field Type Mapping
240
+
241
+ Infer TypeScript/JSON types from test data:
242
+ - String values → `"String"`
243
+ - UUID format → `"UUID"`
244
+ - Date strings (ISO format) → `"Date"` or `"DateTime"`
245
+ - Numbers with decimals → `"Decimal"`
246
+ - Whole numbers → `"Integer"`
247
+ - Boolean values → `"Boolean"`
248
+ - Arrays → `"List"` with element type
249
+
250
+ ## ID Generation
251
+
252
+ **CRITICAL: LinkedId Fields Must Use Real IDs from slice.json**
253
+
254
+ - **linkedId in given/when/then elements**: MUST use the actual element IDs from slice.json
255
+ - Look up command IDs in `slice.json` → `commands` array → `id` field
256
+ - Look up event IDs in `slice.json` → `events` array → `id` field
257
+ - **For STATE_VIEW slices**: Events may not be in the `events` array if they're from other slices
258
+ - Instead, look in `readmodels` → `dependencies` array for entries with `type: "INBOUND"` and `elementType: "EVENT"`
259
+ - Use the `id` field from the dependency entry
260
+ - Look up readmodel IDs in `slice.json` → `readmodels` array → `id` field
261
+ - If element not found in slice.json, use `null` (will be identified as drift)
262
+
263
+ - **Other ID fields**: Generate unique IDs for specification containers
264
+ - Specification `id`: Generate unique ID (e.g., `"GENERATED-SPEC-<counter>"`)
265
+ - Element wrapper `id`: Generate unique ID for each given/when/then element
266
+ - These are just structural IDs, not the element's identity
267
+
268
+ **Example from slice.json:**
269
+ ```json
270
+ {
271
+ "commands": [
272
+ {
273
+ "id": "3458764657124632223", // ← Use THIS for linkedId
274
+ "title": "Activate Shift",
275
+ ...
276
+ }
277
+ ],
278
+ "events": [
279
+ {
280
+ "id": "3458764657005669318", // ← Use THIS for linkedId
281
+ "title": "Shift activated",
282
+ ...
283
+ }
284
+ ]
285
+ }
286
+ ```
287
+
288
+ **Example in code-slice.json:**
289
+ ```json
290
+ {
291
+ "id": "GENERATED-SPEC-001", // ← Specification ID
292
+ "linkedId": "GENERATED-SPEC-001", // ← MUST match specification id
293
+ "when": [
294
+ {
295
+ "title": "Activate Shift",
296
+ "id": "GENERATED-SPEC-CMD-1", // ← Generated wrapper ID
297
+ "linkedId": "3458764657124632223" // ← MUST be real command ID from slice.json
298
+ }
299
+ ]
300
+ }
301
+ ```
302
+
303
+ **Summary of linkedId usage:**
304
+ 1. **Specification level**: `specification.linkedId` = `specification.id` (self-reference)
305
+ 2. **Element level (given/when/then)**:
306
+ - Commands: `element.linkedId` = real ID from `slice.json` → `commands` array
307
+ - Events: `element.linkedId` = real ID from `slice.json` → `events` array OR `readmodels` → `dependencies` (for STATE_VIEW)
308
+ - Readmodels: `element.linkedId` = real ID from `slice.json` → `readmodels` array
309
+ 3. **Error elements (SPEC_ERROR)**: No `linkedId` field (errors don't exist in slice.json)
310
+
311
+ ## Usage
312
+
313
+ Invoke with any test file path:
314
+
315
+ **State-change slices:**
316
+ ```
317
+ /test-analyzer src/slices/ActivateShift/ActivateShift.test.ts
318
+ /test-analyzer src/slices/PlanVacation/PlanVacation.test.ts
319
+ ```
320
+
321
+ **State-view slices (projections):**
322
+ ```
323
+ /test-analyzer src/slices/ActivatedOnlineReservations/OnlineReservationStatus.test.ts
324
+ /test-analyzer src/slices/ActiveShifts/ActiveShifts.test.ts
325
+ ```
326
+
327
+ The skill automatically:
328
+ - Detects slice name from file path
329
+ - Determines if STATE_CHANGE or STATE_VIEW
330
+ - Finds corresponding `.slices/` documentation
331
+ - Generates `code-slice.json` in same directory as test file
332
+
333
+ ## Drift Detection Strategy
334
+
335
+ 1. **Compare test specifications with `slice.json` specifications array**
336
+ 2. **Extract specifications NOT in `slice.json` specifications array:**
337
+ - Happy path scenarios not in specifications
338
+ - Error scenarios (thenThrows) not in specifications
339
+ - Edge cases not in specifications
340
+ - Alternative flows not in specifications
341
+ - **Note:** Even if command/event elements exist in slice.json, if the specification (given/when/then) is not in the specifications array, include it
342
+ 3. **Actions on `code-slice.json`:**
343
+ - All test specs in `slice.json` specifications → Remove/don't create `code-slice.json` (no drift)
344
+ - Test specs NOT in `slice.json` specifications → Generate/update `code-slice.json`
345
+ - No `slice.json` found → Generate `code-slice.json` with all test specifications
346
+
347
+ **Key Principle:**
348
+ - `slice.json` = design documentation (source of truth, READ ONLY)
349
+ - `code-slice.json` = test-derived specifications (generated/deleted by this skill)
350
+ - Drift = test specifications that are NOT in slice.json specifications array
351
+
352
+ ## File Locations
353
+
354
+ - **Test file:** `src/slices/<SliceName>/<SliceName>.test.ts`
355
+ - **Output:** `.slices/<Context>/<folder>/code-slice.json` (same directory as slice.json)
356
+ - **Index lookup:** `.slices/index.json` (find matching slice by title)
357
+ - **Reference:** `.slices/<Context>/<folder>/slice.json` (READ ONLY, from index.json)
358
+
359
+ ## Examples
360
+
361
+ - [STATE_CHANGE Example](examples/state-change-example.md) - Complete example with ActivateShift slice
362
+ - [STATE_VIEW Example](examples/state-view-example.md) - Complete example with OnlineReservationStatus projection
363
+ - [Specification Format](examples/specification-format.md) - Detailed JSON structure and field definitions
364
+
365
+ ## Key Notes
366
+
367
+ - Focus on extracting specifications, not implementation
368
+ - Specifications array contains ALL tests NOT already in `slice.json` specifications array
369
+ - **Remove `code-slice.json` if no drift detected** (all test specs are in slice.json specifications) - keeps codebase clean
370
+ - **Include ALL test specifications** not in slice.json specifications array, including happy path scenarios
371
+ - Drift occurs when test specifications are missing from slice.json specifications array, even if the command/event elements are defined
372
+ - **NEVER modify or delete `slice.json` files** - they are read-only design documentation
373
+ - Generic approach works with any slice - automatically detects name, type, and context