@eventmodelers/cli 1.0.45 → 1.0.46

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 (102) hide show
  1. package/README.md +4 -1
  2. package/cli.js +53 -1
  3. package/package.json +2 -2
  4. package/shared/build-kit/lib/checks/README.md +59 -0
  5. package/shared/build-kit/lib/ralph.js +110 -28
  6. package/shared/build-kit/lib/util/find-slice.cjs +59 -0
  7. package/shared/build-kit/ralph-claude.js +6 -2
  8. package/stacks/blank/templates/build-kit/lib/backend-prompt.md +106 -102
  9. package/stacks/blank/templates/build-kit/lib/prompt.md +102 -106
  10. package/stacks/kurrent/templates/.claude/skills/build-automation/SKILL.md +422 -0
  11. package/stacks/kurrent/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +19 -0
  12. package/stacks/kurrent/templates/.claude/skills/build-automation/references/idempotent-dispatch-patterns.md +65 -0
  13. package/stacks/kurrent/templates/.claude/skills/build-state-change/SKILL.md +418 -0
  14. package/stacks/kurrent/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +41 -0
  15. package/stacks/kurrent/templates/.claude/skills/build-state-change/references/integration-test-patterns.md +64 -0
  16. package/stacks/kurrent/templates/.claude/skills/build-state-view/SKILL.md +391 -0
  17. package/stacks/kurrent/templates/build-kit/CLAUDE.md +122 -0
  18. package/stacks/kurrent/templates/build-kit/lib/AGENT.md +73 -0
  19. package/stacks/kurrent/templates/build-kit/lib/backend-prompt.md +169 -0
  20. package/stacks/kurrent/templates/build-kit/lib/prompt.md +128 -0
  21. package/stacks/kurrent/templates/root/README.md +46 -0
  22. package/stacks/kurrent/templates/root/docker-compose.yml +45 -0
  23. package/stacks/kurrent/templates/root/mvnw +259 -0
  24. package/stacks/kurrent/templates/root/mvnw.cmd +149 -0
  25. package/stacks/kurrent/templates/root/pom.xml +152 -0
  26. package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/QuickstartApplication.java +12 -0
  27. package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/common/EventStore.java +91 -0
  28. package/stacks/kurrent/templates/root/src/main/java/com/example/quickstart/config/KurrentConfiguration.java +42 -0
  29. package/stacks/kurrent/templates/root/src/main/resources/application.properties +14 -0
  30. package/stacks/kurrent/templates/root/src/main/resources/static/index.html +11 -0
  31. package/stacks/node/templates/build-kit/CLAUDE.md +22 -0
  32. package/stacks/node/templates/build-kit/lib/check-commit-scope.cjs +123 -0
  33. package/stacks/node/templates/build-kit/lib/checks/00-blocked-paths.cjs +28 -0
  34. package/stacks/node/templates/build-kit/lib/checks/10-slice-scope.cjs +29 -0
  35. package/stacks/node/templates/build-kit/lib/checks/20-append-only-migrations.cjs +20 -0
  36. package/stacks/node/templates/build-kit/lib/checks/30-test-file-present.cjs +44 -0
  37. package/stacks/node/templates/build-kit/lib/checks/40-no-invented-fields.cjs +92 -0
  38. package/stacks/node/templates/build-kit/lib/checks/50-spec-coverage.cjs +50 -0
  39. package/stacks/node/templates/build-kit/lib/checks/90-tsc-build.cjs +22 -0
  40. package/stacks/node/templates/root/.githooks/pre-commit +11 -0
  41. package/stacks/node/templates/root/package.json +2 -1
  42. package/stacks/node/templates/root/setup-env.sh +7 -1
  43. package/stacks/opencqrs/templates/.claude/skills/build-automation/SKILL.md +434 -0
  44. package/stacks/opencqrs/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +19 -0
  45. package/stacks/opencqrs/templates/.claude/skills/build-automation/references/idempotent-dispatch-patterns.md +62 -0
  46. package/stacks/opencqrs/templates/.claude/skills/build-state-change/SKILL.md +413 -0
  47. package/stacks/opencqrs/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +46 -0
  48. package/stacks/opencqrs/templates/.claude/skills/build-state-change/references/rest-api-patterns.md +145 -0
  49. package/stacks/opencqrs/templates/.claude/skills/build-state-change/references/test-fixture-patterns.md +85 -0
  50. package/stacks/opencqrs/templates/.claude/skills/build-state-view/SKILL.md +354 -0
  51. package/stacks/opencqrs/templates/build-kit/CLAUDE.md +101 -0
  52. package/stacks/opencqrs/templates/build-kit/lib/AGENT.md +58 -0
  53. package/stacks/opencqrs/templates/build-kit/lib/backend-prompt.md +169 -0
  54. package/stacks/opencqrs/templates/build-kit/lib/prompt.md +128 -0
  55. package/stacks/opencqrs/templates/root/README.md +42 -0
  56. package/stacks/opencqrs/templates/root/docker-compose.yml +37 -0
  57. package/stacks/opencqrs/templates/root/mvnw +259 -0
  58. package/stacks/opencqrs/templates/root/mvnw.cmd +149 -0
  59. package/stacks/opencqrs/templates/root/pom.xml +139 -0
  60. package/stacks/opencqrs/templates/root/src/main/java/com/example/quickstart/QuickstartApplication.java +12 -0
  61. package/stacks/opencqrs/templates/root/src/main/java/com/example/quickstart/config/CqrsConfiguration.java +74 -0
  62. package/stacks/opencqrs/templates/root/src/main/resources/application.properties +23 -0
  63. package/stacks/opencqrs/templates/root/src/main/resources/schema.sql +19 -0
  64. package/stacks/opencqrs/templates/root/src/main/resources/static/index.html +11 -0
  65. package/stacks/supabase/templates/build-kit/CLAUDE.md +25 -0
  66. package/stacks/supabase/templates/build-kit/lib/check-commit-scope.cjs +126 -0
  67. package/stacks/supabase/templates/build-kit/lib/checks/00-blocked-paths.cjs +28 -0
  68. package/stacks/supabase/templates/build-kit/lib/checks/10-slice-scope.cjs +34 -0
  69. package/stacks/supabase/templates/build-kit/lib/checks/20-append-only-migrations.cjs +21 -0
  70. package/stacks/supabase/templates/build-kit/lib/checks/30-test-file-present.cjs +44 -0
  71. package/stacks/supabase/templates/build-kit/lib/checks/40-no-invented-fields.cjs +92 -0
  72. package/stacks/supabase/templates/build-kit/lib/checks/50-spec-coverage.cjs +50 -0
  73. package/stacks/supabase/templates/build-kit/lib/checks/90-tsc-build.cjs +22 -0
  74. package/stacks/supabase/templates/root/.githooks/pre-commit +11 -0
  75. package/stacks/supabase/templates/root/package.json +2 -1
  76. package/stacks/supabase/templates/root/setup-env.sh +7 -1
  77. package/stacks/umadb/templates/.claude/skills/build-automation/SKILL.md +313 -0
  78. package/stacks/umadb/templates/.claude/skills/build-automation/references/feature-flag-patterns.md +42 -0
  79. package/stacks/umadb/templates/.claude/skills/build-state-change/SKILL.md +376 -0
  80. package/stacks/umadb/templates/.claude/skills/build-state-change/references/feature-flag-patterns.md +42 -0
  81. package/stacks/umadb/templates/.claude/skills/build-state-change/references/umadb-query-patterns.md +78 -0
  82. package/stacks/umadb/templates/.claude/skills/build-state-view/SKILL.md +338 -0
  83. package/stacks/umadb/templates/build-kit/CLAUDE.md +94 -0
  84. package/stacks/umadb/templates/build-kit/lib/AGENT.md +47 -0
  85. package/stacks/umadb/templates/build-kit/lib/backend-prompt.md +169 -0
  86. package/stacks/umadb/templates/build-kit/lib/prompt.md +128 -0
  87. package/stacks/umadb/templates/root/.mvn/wrapper/maven-wrapper.properties +19 -0
  88. package/stacks/umadb/templates/root/README.md +48 -0
  89. package/stacks/umadb/templates/root/docker-compose.yml +29 -0
  90. package/stacks/umadb/templates/root/mvnw +259 -0
  91. package/stacks/umadb/templates/root/mvnw.cmd +149 -0
  92. package/stacks/umadb/templates/root/pom.xml +151 -0
  93. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/QuickstartApplication.java +12 -0
  94. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/config/UmaDbConfig.java +39 -0
  95. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/DecisionModelLoader.java +76 -0
  96. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/EventCodec.java +40 -0
  97. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/EventDispatcher.java +100 -0
  98. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/OptimisticConcurrencyException.java +14 -0
  99. package/stacks/umadb/templates/root/src/main/java/io/umadb/quickstart/eventstore/SliceEventListener.java +23 -0
  100. package/stacks/umadb/templates/root/src/main/resources/application.properties +17 -0
  101. package/stacks/umadb/templates/root/src/test/java/io/umadb/quickstart/testsupport/InMemoryUmaDbClient.java +135 -0
  102. package/stacks/umadb/templates/root/src/test/resources/application.properties +8 -0
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ // Everything staged in a slice commit must be inside the slice's own folder, or
4
+ // one of the documented shared-infra exceptions a slice legitimately registers
5
+ // *into* (never rewrites wholesale). See build-kit/lib/AGENT.md and the
6
+ // build-state-view/build-automation SKILL.md files for what a compliant edit to
7
+ // an exception file looks like. Migration files have their own dedicated check
8
+ // (20-append-only-migrations.cjs), so they're allowed through here.
9
+
10
+ const ALLOWED_EXCEPTIONS = [
11
+ /^src\/slices\/[^/]+\/[A-Za-z0-9]+Events\.ts$/, // per-context event union (append-only)
12
+ /^src\/common\/loadPostgresEventstore\.ts$/, // projection registration / schema.migrate()
13
+ ];
14
+
15
+ const MIGRATION_PATTERN = /^migrations\/V\d+__.*\.sql$/;
16
+
17
+ module.exports = {
18
+ name: 'slice-scope',
19
+ run(ctx) {
20
+ const violations = [];
21
+ for (const { path: p } of ctx.changes) {
22
+ if (ctx.SLICE_PATTERN.test(p)) continue;
23
+ if (MIGRATION_PATTERN.test(p)) continue;
24
+ if (ALLOWED_EXCEPTIONS.some((r) => r.test(p))) continue;
25
+ violations.push({ path: p, reason: 'outside src/slices/{context}/{slice}/ and not a documented exception' });
26
+ }
27
+ return violations;
28
+ },
29
+ };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ // migrations/V{n}__*.sql may only be ADDED, never modified or deleted — migrations
4
+ // are append-only; a fix belongs in a new migration, not an edit to an old one.
5
+
6
+ const MIGRATION_PATTERN = /^migrations\/V\d+__.*\.sql$/;
7
+
8
+ module.exports = {
9
+ name: 'append-only-migrations',
10
+ run(ctx) {
11
+ const violations = [];
12
+ for (const { status, path: p } of ctx.changes) {
13
+ if (!MIGRATION_PATTERN.test(p)) continue;
14
+ if (status !== 'A') {
15
+ violations.push({ path: p, reason: 'existing migrations are append-only; add a new V{n} file instead of editing this one' });
16
+ }
17
+ }
18
+ return violations;
19
+ },
20
+ };
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ // A slice commit that adds/changes a command handler, projection, or processor
4
+ // must also include a *.test.ts for that slice — catches the agent skipping the
5
+ // matching build skill's test step (build-state-change Step 4 / build-state-view
6
+ // Step 5 / build-automation's DeciderSpecification tests).
7
+
8
+ const { execSync } = require('child_process');
9
+
10
+ const IMPLEMENTATION_FILE = /^src\/slices\/([^/]+)\/([^/]+)\/(?:[A-Za-z0-9]+Command|[A-Za-z0-9]+Projection|processor(?:-[A-Za-z0-9]+)?)\.ts$/;
11
+
12
+ module.exports = {
13
+ name: 'test-file-present',
14
+ run(ctx) {
15
+ const sliceDirs = new Set();
16
+ for (const { path: p } of ctx.changes) {
17
+ const m = IMPLEMENTATION_FILE.exec(p);
18
+ if (m) sliceDirs.add(`src/slices/${m[1]}/${m[2]}`);
19
+ }
20
+ if (sliceDirs.size === 0) return [];
21
+
22
+ let tracked = [];
23
+ try {
24
+ tracked = execSync('git ls-files -- src/slices', { cwd: ctx.repoRoot, encoding: 'utf8' })
25
+ .split('\n')
26
+ .filter(Boolean);
27
+ } catch {
28
+ // best-effort — fall through with whatever's staged
29
+ }
30
+ const known = new Set([...tracked, ...ctx.changes.map((c) => c.path)]);
31
+
32
+ const violations = [];
33
+ for (const dir of sliceDirs) {
34
+ const hasTest = [...known].some((f) => f.startsWith(`${dir}/`) && f.endsWith('.test.ts'));
35
+ if (!hasTest) {
36
+ violations.push({
37
+ path: dir,
38
+ reason: 'no *.test.ts found for this slice — command handlers/projections/processors need test coverage',
39
+ });
40
+ }
41
+ }
42
+ return violations;
43
+ },
44
+ };
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ // Heuristic: flags a field name used in a slice's Command/ReadModel type literal
4
+ // that doesn't appear anywhere in that slice's own slice.json. Not a real
5
+ // TS/schema-aware check (no parser dependency) — it regex-extracts the fields
6
+ // declared inside `Command<'Name', { ... }, ...>` and `type XReadModel = { ... }`
7
+ // literals, so unusual formatting (nested object/array field types, nonstandard
8
+ // generics) can slip past undetected. It only ever adds violations for fields it
9
+ // is confident about; when slice.json can't be found/parsed for a slice, that
10
+ // slice's files are skipped entirely rather than guessed at.
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { findSliceJson, normalize } = require('../util/find-slice.cjs');
15
+
16
+ const COMMON_ALLOWED = new Set(
17
+ ['id', 'userId', 'correlationId', 'causationId', 'streamName', 'type', 'data', 'metadata', 'createdAt', 'updatedAt', 'timestamp'].map(normalize),
18
+ );
19
+
20
+ const TYPE_LITERALS = [
21
+ /(?:Command|Event)<\s*'[^']+'\s*,\s*{([^}]*)}/g,
22
+ /type\s+[A-Za-z0-9_]+ReadModel\s*=\s*{([^}]*)}/g,
23
+ ];
24
+
25
+ const FIELD_LINE = /^\s*([A-Za-z_][A-Za-z0-9_]*)\??\s*:/gm;
26
+
27
+ function collectDeclaredFields(node, out) {
28
+ if (Array.isArray(node)) {
29
+ for (const item of node) collectDeclaredFields(item, out);
30
+ } else if (node && typeof node === 'object') {
31
+ if (typeof node.name === 'string' && ('type' in node || 'optional' in node)) {
32
+ out.add(normalize(node.name));
33
+ }
34
+ for (const key of Object.keys(node)) collectDeclaredFields(node[key], out);
35
+ }
36
+ }
37
+
38
+ module.exports = {
39
+ name: 'no-invented-fields',
40
+ run(ctx) {
41
+ const bySlice = new Map(); // "context/SliceName" -> { context, sliceName, files: [] }
42
+
43
+ for (const { path: p } of ctx.changes) {
44
+ if (!p.endsWith('.ts') || p.endsWith('.test.ts')) continue;
45
+ const m = /^src\/slices\/([^/]+)\/([^/]+)\//.exec(p);
46
+ if (!m) continue;
47
+ const key = `${m[1]}/${m[2]}`;
48
+ if (!bySlice.has(key)) bySlice.set(key, { context: m[1], sliceName: m[2], files: [] });
49
+ bySlice.get(key).files.push(p);
50
+ }
51
+
52
+ const violations = [];
53
+
54
+ for (const { context, sliceName, files } of bySlice.values()) {
55
+ const slice = findSliceJson(ctx.repoRoot, context, sliceName);
56
+ if (!slice) continue; // can't verify — don't block
57
+
58
+ const declared = new Set();
59
+ collectDeclaredFields(slice, declared);
60
+ if (declared.size === 0) continue; // slice.json shape not recognized — don't block
61
+
62
+ for (const file of files) {
63
+ let content;
64
+ try {
65
+ content = fs.readFileSync(path.join(ctx.repoRoot, file), 'utf8');
66
+ } catch {
67
+ continue; // deleted/unreadable — nothing to check
68
+ }
69
+
70
+ for (const pattern of TYPE_LITERALS) {
71
+ pattern.lastIndex = 0;
72
+ let typeMatch;
73
+ while ((typeMatch = pattern.exec(content))) {
74
+ FIELD_LINE.lastIndex = 0;
75
+ let fieldMatch;
76
+ while ((fieldMatch = FIELD_LINE.exec(typeMatch[1]))) {
77
+ const raw = fieldMatch[1];
78
+ const norm = normalize(raw);
79
+ if (COMMON_ALLOWED.has(norm) || declared.has(norm)) continue;
80
+ violations.push({
81
+ path: file,
82
+ reason: `field "${raw}" is not declared anywhere in slice.json for this slice — check for an invented field`,
83
+ });
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ return violations;
91
+ },
92
+ };
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ // Heuristic: a slice's *.test.ts must have at least as many `it(...)` blocks as
4
+ // slice.json has `specifications[]` entries. Doesn't verify each spec is
5
+ // actually tested (that would need matching scenario content, not just count),
6
+ // just that nobody's silently short a test case. Skipped (not blocked) when
7
+ // slice.json can't be found or has no specifications[] array.
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { findSliceJson } = require('../util/find-slice.cjs');
12
+
13
+ const TEST_FILE = /^src\/slices\/([^/]+)\/([^/]+)\/[^/]+\.test\.ts$/;
14
+ const IT_BLOCK = /\bit(?:\.(?:only|skip))?\s*\(/g;
15
+
16
+ module.exports = {
17
+ name: 'spec-coverage',
18
+ run(ctx) {
19
+ const violations = [];
20
+
21
+ for (const { path: p } of ctx.changes) {
22
+ TEST_FILE.lastIndex = 0;
23
+ const m = TEST_FILE.exec(p);
24
+ if (!m) continue;
25
+ const [, context, sliceName] = m;
26
+
27
+ const slice = findSliceJson(ctx.repoRoot, context, sliceName);
28
+ if (!slice || !Array.isArray(slice.specifications) || slice.specifications.length === 0) continue;
29
+
30
+ let content;
31
+ try {
32
+ content = fs.readFileSync(path.join(ctx.repoRoot, p), 'utf8');
33
+ } catch {
34
+ continue; // deleted — nothing to check
35
+ }
36
+
37
+ const specCount = slice.specifications.length;
38
+ const itCount = (content.match(IT_BLOCK) || []).length;
39
+
40
+ if (itCount < specCount) {
41
+ violations.push({
42
+ path: p,
43
+ reason: `slice.json declares ${specCount} specification(s) but this test file only has ${itCount} it(...) block(s)`,
44
+ });
45
+ }
46
+ }
47
+
48
+ return violations;
49
+ },
50
+ };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ // The project must still typecheck after the slice change. Uses the project's
4
+ // own `typescript` devDependency via `npx tsc --noEmit` — no new dependency,
5
+ // but it does mean this check needs node_modules already installed (same
6
+ // prerequisite `npm run build` already has).
7
+
8
+ const { execSync } = require('child_process');
9
+
10
+ module.exports = {
11
+ name: 'tsc-build',
12
+ skipIfAlreadyFailing: true, // slow — don't bother once the commit is rejected already
13
+ run(ctx) {
14
+ try {
15
+ execSync('npx tsc --noEmit', { cwd: ctx.repoRoot, stdio: 'pipe' });
16
+ return [];
17
+ } catch (err) {
18
+ const output = String(err.stdout || err.message || '').trim().split('\n').slice(0, 20).join('\n');
19
+ return [{ path: '(tsc --noEmit)', reason: `TypeScript build failed:\n${output}` }];
20
+ }
21
+ },
22
+ };
@@ -0,0 +1,11 @@
1
+ #!/bin/sh
2
+ # Installed via setup-env.sh (`git config core.hooksPath .githooks`), so this file
3
+ # is versioned and shared by every clone instead of living only in .git/hooks/.
4
+ set -e
5
+
6
+ repo_root=$(git rev-parse --show-toplevel)
7
+ script="$repo_root/.build-kit/lib/check-commit-scope.cjs"
8
+
9
+ if [ -f "$script" ]; then
10
+ node "$script"
11
+ fi
@@ -7,7 +7,8 @@
7
7
  "dev": "node --env-file=.env --require ts-node/register server.ts",
8
8
  "build": "tsc",
9
9
  "start": "NODE_ENV=production node --env-file=.env --require ts-node/register server.ts",
10
- "test": "tsx --test 'src/**/*.test.ts'"
10
+ "test": "tsx --test 'src/**/*.test.ts'",
11
+ "check:scope": "node .build-kit/lib/check-commit-scope.cjs"
11
12
  },
12
13
  "dependencies": {
13
14
  "@event-driven-io/emmett": "^0.42.1-alpha.1",
@@ -50,4 +50,10 @@ FLYWAY_PASSWORD=${DB_PASSWORD}
50
50
  EOF
51
51
 
52
52
  echo ""
53
- echo ".env created successfully."
53
+ echo ".env created successfully."
54
+
55
+ if [ -f .githooks/pre-commit ] && git rev-parse --git-dir >/dev/null 2>&1; then
56
+ chmod +x .githooks/pre-commit 2>/dev/null || true
57
+ git config core.hooksPath .githooks
58
+ echo "Configured git to use .githooks/ (slice commit-scope guard)."
59
+ fi