eva4j 1.0.17 → 1.0.18

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 (134) hide show
  1. package/AGENTS.md +2 -0
  2. package/DOMAIN_YAML_GUIDE.md +3 -1
  3. package/QUICK_REFERENCE.md +8 -4
  4. package/bin/eva4j.js +70 -2
  5. package/config/defaults.json +1 -0
  6. package/docs/CAMUNDA_DMN_GUIDE.md +1380 -0
  7. package/docs/KAFKA_PRODUCTION_CONFIG.md +441 -0
  8. package/docs/RABBITMQ_PRODUCTION_CONFIG.md +227 -0
  9. package/docs/commands/ADD_RABBITMQ_CLIENT.md +192 -0
  10. package/docs/commands/EVALUATE_SYSTEM.md +272 -8
  11. package/docs/commands/GENERATE_RABBITMQ_EVENT.md +341 -0
  12. package/docs/commands/GENERATE_RABBITMQ_LISTENER.md +595 -0
  13. package/docs/commands/GENERATE_TEMPORAL_FLOW.md +52 -12
  14. package/docs/commands/INDEX.md +27 -3
  15. package/docs/prototype/TEMPORAL_COMMUNICATION_PATTERNS.md +731 -0
  16. package/docs/prototype/TEMPORAL_DESIGN_METHODOLOGY.md +740 -0
  17. package/docs/prototype/system/RISKS.md +277 -0
  18. package/docs/prototype/system/customers.yaml +133 -0
  19. package/docs/prototype/system/inventory.yaml +109 -0
  20. package/docs/prototype/system/notifications.yaml +131 -0
  21. package/docs/prototype/system/orders.yaml +241 -0
  22. package/docs/prototype/system/payments.yaml +256 -0
  23. package/docs/prototype/system/products.yaml +168 -0
  24. package/docs/prototype/system/system.yaml +269 -0
  25. package/examples/domain-endpoints-multi-aggregate.yaml +140 -0
  26. package/examples/domain-read-models.yaml +2 -2
  27. package/examples/system/customer.yaml +89 -0
  28. package/examples/system/orders.yaml +119 -0
  29. package/examples/system/product.yaml +27 -0
  30. package/examples/system/system.yaml +80 -0
  31. package/package.json +1 -1
  32. package/src/agents/design-gap-analyst-temporal.agent.md +452 -0
  33. package/src/agents/design-gap-analyst.agent.md +383 -0
  34. package/src/agents/design-reviewer-temporal.agent.md +412 -0
  35. package/src/agents/design-reviewer.agent.md +31 -5
  36. package/src/agents/implement-use-cases.prompt.md +179 -0
  37. package/src/agents/ux-gap-analyst.agent.md +412 -0
  38. package/src/commands/add-rabbitmq-client.js +261 -0
  39. package/src/commands/add-temporal-client.js +22 -2
  40. package/src/commands/build.js +267 -11
  41. package/src/commands/evaluate-system.js +700 -13
  42. package/src/commands/generate-entities.js +308 -16
  43. package/src/commands/generate-rabbitmq-event.js +665 -0
  44. package/src/commands/generate-rabbitmq-listener.js +205 -0
  45. package/src/commands/generate-temporal-activity.js +968 -34
  46. package/src/commands/generate-temporal-flow.js +95 -38
  47. package/src/commands/generate-temporal-system.js +708 -0
  48. package/src/skills/build-system-yaml/SKILL.md +222 -2
  49. package/src/skills/build-system-yaml/references/domain-yaml-spec.md +50 -4
  50. package/src/skills/build-system-yaml/references/module-spec.md +57 -8
  51. package/src/skills/build-temporal-system/SKILL.md +752 -0
  52. package/src/skills/build-temporal-system/references/temporal-communication-patterns.md +167 -0
  53. package/src/skills/build-temporal-system/references/temporal-domain-yaml-spec.md +449 -0
  54. package/src/skills/build-temporal-system/references/temporal-module-spec.md +353 -0
  55. package/src/skills/build-temporal-system/references/temporal-system-yaml-spec.md +326 -0
  56. package/src/skills/implement-use-case/SKILL.md +350 -0
  57. package/src/skills/implement-use-case/references/use-case-patterns.md +980 -0
  58. package/src/skills/requirements-elicitation/SKILL.md +228 -0
  59. package/src/skills/requirements-elicitation/references/interview-framework.md +260 -0
  60. package/src/skills/requirements-elicitation/references/output-templates.md +368 -0
  61. package/src/utils/bounded-context-diagram.js +844 -0
  62. package/src/utils/domain-validator.js +266 -4
  63. package/src/utils/naming.js +10 -0
  64. package/src/utils/system-validator.js +169 -11
  65. package/src/utils/system-yaml-parser.js +318 -0
  66. package/src/utils/temporal-validator.js +497 -0
  67. package/src/utils/yaml-to-entity.js +10 -7
  68. package/templates/aggregate/DomainEventHandler.java.ejs +116 -22
  69. package/templates/aggregate/JpaAggregateRoot.java.ejs +2 -2
  70. package/templates/aggregate/JpaEntity.java.ejs +2 -2
  71. package/templates/base/docker/rabbitmq-services.yaml.ejs +12 -0
  72. package/templates/base/resources/parameters/develop/kafka.yaml.ejs +5 -0
  73. package/templates/base/resources/parameters/develop/rabbitmq.yaml.ejs +15 -0
  74. package/templates/base/resources/parameters/develop/temporal.yaml.ejs +0 -3
  75. package/templates/base/resources/parameters/local/kafka.yaml.ejs +5 -0
  76. package/templates/base/resources/parameters/local/rabbitmq.yaml.ejs +15 -0
  77. package/templates/base/resources/parameters/local/temporal.yaml.ejs +0 -3
  78. package/templates/base/resources/parameters/production/kafka.yaml.ejs +39 -8
  79. package/templates/base/resources/parameters/production/rabbitmq.yaml.ejs +32 -0
  80. package/templates/base/resources/parameters/production/temporal.yaml.ejs +0 -3
  81. package/templates/base/resources/parameters/test/kafka.yaml.ejs +12 -6
  82. package/templates/base/resources/parameters/test/rabbitmq.yaml.ejs +15 -0
  83. package/templates/base/resources/parameters/test/temporal.yaml.ejs +0 -3
  84. package/templates/base/root/AGENTS.md.ejs +1 -1
  85. package/templates/crud/EndpointsController.java.ejs +1 -1
  86. package/templates/crud/ScaffoldCommand.java.ejs +5 -2
  87. package/templates/crud/ScaffoldCommandHandler.java.ejs +3 -1
  88. package/templates/crud/ScaffoldQuery.java.ejs +5 -2
  89. package/templates/crud/ScaffoldQueryHandler.java.ejs +3 -1
  90. package/templates/crud/SubEntityRemoveCommand.java.ejs +1 -1
  91. package/templates/evaluate/report.html.ejs +1447 -90
  92. package/templates/kafka-event/KafkaConfigBean.java.ejs +1 -1
  93. package/templates/kafka-event/KafkaMessageBroker.java.ejs +3 -3
  94. package/templates/ports/PortAclMapper.java.ejs +35 -0
  95. package/templates/ports/PortFeignAdapter.java.ejs +7 -22
  96. package/templates/ports/PortFeignClient.java.ejs +4 -0
  97. package/templates/ports/PortResponseDto.java.ejs +1 -1
  98. package/templates/rabbitmq-event/RabbitConfigBean.java.ejs +33 -0
  99. package/templates/rabbitmq-event/RabbitConfigExchange.java.ejs +12 -0
  100. package/templates/rabbitmq-event/RabbitMessageBroker.java.ejs +35 -0
  101. package/templates/rabbitmq-event/RabbitMessageBrokerMethod.java.ejs +9 -0
  102. package/templates/rabbitmq-listener/RabbitConfigConsumerBean.java.ejs +33 -0
  103. package/templates/rabbitmq-listener/RabbitConfigConsumerExchange.java.ejs +12 -0
  104. package/templates/rabbitmq-listener/RabbitListenerClass.java.ejs +82 -0
  105. package/templates/rabbitmq-listener/RabbitListenerSimple.java.ejs +56 -0
  106. package/templates/read-model/ReadModelJpa.java.ejs +1 -1
  107. package/templates/read-model/ReadModelJpaRepository.java.ejs +2 -0
  108. package/templates/read-model/ReadModelRabbitListener.java.ejs +71 -0
  109. package/templates/read-model/ReadModelRepositoryImpl.java.ejs +9 -5
  110. package/templates/read-model/ReadModelSyncHandler.java.ejs +2 -0
  111. package/templates/shared/configurations/kafkaConfig/KafkaConfig.java.ejs +18 -4
  112. package/templates/shared/configurations/rabbitmqConfig/RabbitMQConfig.java.ejs +100 -0
  113. package/templates/shared/configurations/temporalConfig/TemporalConfig.java.ejs +2 -64
  114. package/templates/shared/configurations/temporalConfig/TemporalWorkerFactoryLifecycle.java.ejs +41 -0
  115. package/templates/temporal-activity/ActivityImpl.java.ejs +68 -2
  116. package/templates/temporal-activity/ActivityInput.java.ejs +14 -0
  117. package/templates/temporal-activity/ActivityInterface.java.ejs +7 -1
  118. package/templates/temporal-activity/ActivityOutput.java.ejs +14 -0
  119. package/templates/temporal-activity/NestedType.java.ejs +12 -0
  120. package/templates/temporal-activity/SharedActivityInput.java.ejs +14 -0
  121. package/templates/temporal-activity/SharedActivityInterface.java.ejs +15 -0
  122. package/templates/temporal-activity/SharedActivityOutput.java.ejs +14 -0
  123. package/templates/temporal-activity/SharedNestedType.java.ejs +12 -0
  124. package/templates/temporal-flow/ModuleHeavyActivity.java.ejs +6 -0
  125. package/templates/temporal-flow/ModuleLightActivity.java.ejs +6 -0
  126. package/templates/temporal-flow/ModuleTemporalWorkerConfig.java.ejs +58 -0
  127. package/templates/temporal-flow/WorkFlowImpl.java.ejs +172 -12
  128. package/templates/temporal-flow/WorkFlowInput.java.ejs +11 -0
  129. package/templates/temporal-flow/WorkFlowInterface.java.ejs +5 -4
  130. package/templates/temporal-flow/WorkFlowService.java.ejs +42 -12
  131. package/COMMAND_EVALUATION.md +0 -911
  132. package/test-c2010.js +0 -49
  133. package/test-update-compat.js +0 -109
  134. package/test-update-lifecycle.js +0 -121
@@ -0,0 +1,318 @@
1
+ /**
2
+ * system-yaml-parser.js
3
+ *
4
+ * Parses system.yaml and resolves cross-module workflow definitions by
5
+ * cross-referencing each workflow step's activity with its target module's
6
+ * domain.yaml. Produces a rich context tree for template generation.
7
+ */
8
+
9
+ const yaml = require('js-yaml');
10
+ const fs = require('fs-extra');
11
+ const path = require('path');
12
+ const chalk = require('chalk');
13
+ const { toPascalCase, toCamelCase, toScreamingSnakeCase } = require('./naming');
14
+
15
+ // ─── Java type → import mapping (shared with generate-temporal-activity) ────
16
+ const JAVA_TYPE_IMPORTS = {
17
+ BigDecimal: 'java.math.BigDecimal',
18
+ LocalDateTime: 'java.time.LocalDateTime',
19
+ LocalDate: 'java.time.LocalDate',
20
+ LocalTime: 'java.time.LocalTime',
21
+ Instant: 'java.time.Instant',
22
+ UUID: 'java.util.UUID',
23
+ List: 'java.util.List',
24
+ };
25
+
26
+ function resolveFieldImports(fields) {
27
+ const imports = new Set();
28
+ for (const field of fields) {
29
+ for (const [type, imp] of Object.entries(JAVA_TYPE_IMPORTS)) {
30
+ if (field.javaType && field.javaType.includes(type)) {
31
+ imports.add(`import ${imp};`);
32
+ }
33
+ }
34
+ }
35
+ return Array.from(imports).sort();
36
+ }
37
+
38
+ function mapField(field) {
39
+ let javaType = field.type || 'String';
40
+ // Default bare collection types to generic <String>
41
+ if (javaType === 'List' || javaType === 'Set') {
42
+ javaType = javaType + '<String>';
43
+ }
44
+ return { name: field.name, javaType };
45
+ }
46
+
47
+ /**
48
+ * Parse a timeout string like "5s", "10m", "2h" into a Duration descriptor.
49
+ * @param {string|null} timeout
50
+ * @returns {{ value: number, unit: string }}
51
+ */
52
+ function parseTimeout(timeout) {
53
+ if (!timeout) return { value: 30, unit: 'Seconds' };
54
+ const match = String(timeout).match(/^(\d+)(s|m|h)$/);
55
+ if (!match) return { value: 30, unit: 'Seconds' };
56
+ const units = { s: 'Seconds', m: 'Minutes', h: 'Hours' };
57
+ return { value: parseInt(match[1], 10), unit: units[match[2]] || 'Seconds' };
58
+ }
59
+
60
+ // ─── Core parser ────────────────────────────────────────────────────────────
61
+
62
+ /**
63
+ * Load and parse system.yaml.
64
+ * @param {string} systemYamlPath - Absolute path to system.yaml
65
+ * @returns {object} Raw parsed YAML data
66
+ */
67
+ async function loadSystemYaml(systemYamlPath) {
68
+ if (!(await fs.pathExists(systemYamlPath))) {
69
+ throw new Error(`system.yaml not found at ${systemYamlPath}`);
70
+ }
71
+ const content = await fs.readFile(systemYamlPath, 'utf-8');
72
+ return yaml.load(content);
73
+ }
74
+
75
+ /**
76
+ * Load the activities section from a module's domain.yaml.
77
+ * @param {string} domainYamlPath - Absolute path to domain.yaml
78
+ * @returns {Map<string, object>} Map of PascalCase activity name → raw activity definition
79
+ */
80
+ async function loadModuleActivities(domainYamlPath) {
81
+ const activities = new Map();
82
+ if (!(await fs.pathExists(domainYamlPath))) return activities;
83
+
84
+ const content = await fs.readFile(domainYamlPath, 'utf-8');
85
+ const data = yaml.load(content);
86
+ if (!data || !Array.isArray(data.activities)) return activities;
87
+
88
+ for (const act of data.activities) {
89
+ activities.set(toPascalCase(act.name), act);
90
+ }
91
+ return activities;
92
+ }
93
+
94
+ /**
95
+ * Parse system.yaml and resolve all workflow steps against their target module
96
+ * domain.yaml files.
97
+ *
98
+ * @param {string} systemDir - Directory containing system.yaml and domain yamls
99
+ * @returns {object} Parsed system context:
100
+ * {
101
+ * system: { name, groupId, javaVersion, springBootVersion, database },
102
+ * orchestration: { enabled, engine, temporal: { target, namespace } },
103
+ * modules: [{ name, description, exposes }],
104
+ * workflows: [ResolvedWorkflow],
105
+ * activityRegistry: Map<activityName, { module, definition }>,
106
+ * warnings: string[]
107
+ * }
108
+ */
109
+ async function parseSystemYaml(systemDir) {
110
+ const systemYamlPath = path.join(systemDir, 'system.yaml');
111
+ const data = await loadSystemYaml(systemYamlPath);
112
+
113
+ const warnings = [];
114
+
115
+ // ── 1. Basic sections ──────────────────────────────────────────────────
116
+ const system = data.system || {};
117
+ const orchestration = data.orchestration || {};
118
+ const modules = Array.isArray(data.modules) ? data.modules : [];
119
+ const rawWorkflows = Array.isArray(data.workflows) ? data.workflows : [];
120
+
121
+ // ── 2. Load all module activities ──────────────────────────────────────
122
+ // Map: moduleName (camelCase) → Map<ActivityPascalCase, rawDefinition>
123
+ const moduleActivitiesMap = new Map();
124
+
125
+ for (const mod of modules) {
126
+ const modCamel = toCamelCase(mod.name);
127
+ // Domain YAML files can be in the system dir (prototype layout)
128
+ // or in the project's module directory. Try both.
129
+ const candidates = [
130
+ path.join(systemDir, `${mod.name}.yaml`),
131
+ path.join(systemDir, `${modCamel}.yaml`),
132
+ ];
133
+
134
+ let activities = new Map();
135
+ for (const candidate of candidates) {
136
+ activities = await loadModuleActivities(candidate);
137
+ if (activities.size > 0) break;
138
+ }
139
+
140
+ moduleActivitiesMap.set(modCamel, activities);
141
+ }
142
+
143
+ // ── 3. Build activity registry (flat, for easy lookup) ─────────────────
144
+ // Map: activityPascalCase → { module (camelCase), definition, inputFields, outputFields }
145
+ const activityRegistry = new Map();
146
+
147
+ for (const [modName, activities] of moduleActivitiesMap) {
148
+ for (const [actName, actDef] of activities) {
149
+ activityRegistry.set(actName, {
150
+ module: modName,
151
+ definition: actDef,
152
+ inputFields: Array.isArray(actDef.input) ? actDef.input.map(mapField) : [],
153
+ outputFields: Array.isArray(actDef.output) ? actDef.output.map(mapField) : [],
154
+ type: (actDef.type || 'light').toLowerCase(),
155
+ compensation: actDef.compensation ? toPascalCase(actDef.compensation) : null,
156
+ timeout: actDef.timeout || null,
157
+ description: actDef.description || '',
158
+ });
159
+ }
160
+ }
161
+
162
+ // ── 4. Resolve workflows ───────────────────────────────────────────────
163
+ const workflows = rawWorkflows.map((wf) =>
164
+ resolveWorkflow(wf, activityRegistry, warnings)
165
+ );
166
+
167
+ return {
168
+ system,
169
+ orchestration,
170
+ modules,
171
+ workflows,
172
+ activityRegistry,
173
+ warnings,
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Resolve a single workflow definition: enrich each step with activity
179
+ * definitions, detect parallel groups, and derive the host module.
180
+ *
181
+ * @param {object} wf - Raw workflow from system.yaml
182
+ * @param {Map} activityRegistry - Global activity lookup
183
+ * @param {string[]} warnings - Mutable array for warnings
184
+ * @returns {object} Resolved workflow
185
+ */
186
+ function resolveWorkflow(wf, activityRegistry, warnings) {
187
+ const name = wf.name;
188
+ const namePascal = toPascalCase(name);
189
+ const trigger = wf.trigger || {};
190
+ const hostModule = toCamelCase(trigger.module || '');
191
+ const taskQueue = wf.taskQueue || `${toScreamingSnakeCase(hostModule)}_WORKFLOW_QUEUE`;
192
+ const isSaga = wf.saga === true;
193
+
194
+ const steps = [];
195
+ const rawSteps = Array.isArray(wf.steps) ? wf.steps : [];
196
+
197
+ for (let i = 0; i < rawSteps.length; i++) {
198
+ const rawStep = rawSteps[i];
199
+ const activityName = toPascalCase(rawStep.activity);
200
+ const targetModule = toCamelCase(rawStep.target || hostModule);
201
+ const isLocal = targetModule === hostModule;
202
+ const isAsync = rawStep.type === 'async';
203
+ const isParallel = rawStep.parallel === true;
204
+ const isOptional = rawStep.optional === true;
205
+
206
+ // Resolve from registry
207
+ const registered = activityRegistry.get(activityName);
208
+ if (!registered) {
209
+ warnings.push(
210
+ `Workflow '${name}' step ${i + 1}: activity '${activityName}' not found in any module's domain.yaml`
211
+ );
212
+ }
213
+
214
+ const inputFields = registered ? registered.inputFields : (rawStep.input || []).map((n) => ({ name: n, javaType: 'String' }));
215
+ const outputFields = registered ? registered.outputFields : (rawStep.output || []).map((n) => ({ name: n, javaType: 'String' }));
216
+ const actType = registered ? registered.type : (rawStep.type === 'async' ? 'light' : 'light');
217
+ const timeout = rawStep.timeout || (registered ? registered.timeout : null);
218
+
219
+ // Resolve compensation
220
+ let compensation = null;
221
+ if (rawStep.compensation) {
222
+ const compName = toPascalCase(rawStep.compensation);
223
+ const compRegistered = activityRegistry.get(compName);
224
+ compensation = {
225
+ name: compName,
226
+ module: compRegistered ? compRegistered.module : targetModule,
227
+ inputFields: compRegistered ? compRegistered.inputFields : [],
228
+ };
229
+ }
230
+
231
+ // Queue for this step's target module
232
+ const stepQueue = actType === 'heavy'
233
+ ? `${toScreamingSnakeCase(targetModule)}_HEAVY_TASK_QUEUE`
234
+ : `${toScreamingSnakeCase(targetModule)}_LIGHT_TASK_QUEUE`;
235
+
236
+ // Raw field names from system.yaml (wiring-level names)
237
+ const rawInputNames = Array.isArray(rawStep.input) ? rawStep.input : [];
238
+ const rawOutputNames = Array.isArray(rawStep.output) ? rawStep.output : [];
239
+
240
+ steps.push({
241
+ index: i,
242
+ activityName,
243
+ activityCamel: activityName.charAt(0).toLowerCase() + activityName.slice(1),
244
+ targetModule,
245
+ targetModulePascal: toPascalCase(targetModule),
246
+ targetModuleScreamingSnake: toScreamingSnakeCase(targetModule),
247
+ stepQueue,
248
+ isLocal,
249
+ isAsync,
250
+ isParallel,
251
+ isOptional,
252
+ actType,
253
+ timeout,
254
+ inputFields,
255
+ outputFields,
256
+ rawInputNames,
257
+ rawOutputNames,
258
+ hasInput: inputFields.length > 0,
259
+ hasOutput: outputFields.length > 0,
260
+ compensation,
261
+ inputImports: resolveFieldImports(inputFields),
262
+ outputImports: resolveFieldImports(outputFields),
263
+ });
264
+ }
265
+
266
+ // ── Detect parallel groups ─────────────────────────────────────────────
267
+ // Consecutive steps with parallel: true form a group
268
+ const parallelGroups = [];
269
+ let currentGroup = null;
270
+ for (const step of steps) {
271
+ if (step.isParallel) {
272
+ if (!currentGroup) {
273
+ currentGroup = { startIndex: step.index, steps: [] };
274
+ }
275
+ currentGroup.steps.push(step);
276
+ } else {
277
+ if (currentGroup) {
278
+ parallelGroups.push(currentGroup);
279
+ currentGroup = null;
280
+ }
281
+ }
282
+ }
283
+ if (currentGroup) parallelGroups.push(currentGroup);
284
+
285
+ // ── Collect unique target modules (for imports) ────────────────────────
286
+ const targetModules = [...new Set(steps.map((s) => s.targetModule))];
287
+
288
+ return {
289
+ name,
290
+ namePascal,
291
+ nameScreamingSnake: toScreamingSnakeCase(name.replace(/Workflow$/, '')),
292
+ trigger,
293
+ hostModule,
294
+ hostModulePascal: toPascalCase(hostModule),
295
+ hostModuleScreamingSnake: toScreamingSnakeCase(hostModule),
296
+ taskQueue,
297
+ isSaga,
298
+ steps,
299
+ parallelGroups,
300
+ targetModules,
301
+ hasParallelSteps: parallelGroups.length > 0,
302
+ hasCompensations: steps.some((s) => s.compensation !== null),
303
+ hasAsyncSteps: steps.some((s) => s.isAsync),
304
+ hasOptionalSteps: steps.some((s) => s.isOptional),
305
+ };
306
+ }
307
+
308
+ // ─── Exports ────────────────────────────────────────────────────────────────
309
+
310
+ module.exports = {
311
+ parseSystemYaml,
312
+ loadSystemYaml,
313
+ loadModuleActivities,
314
+ resolveWorkflow,
315
+ resolveFieldImports,
316
+ mapField,
317
+ parseTimeout,
318
+ };