@specverse/engines 4.1.13 → 4.1.15

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 (68) hide show
  1. package/assets/prompts/core/standard/v9/behavior.prompt.yaml +126 -0
  2. package/dist/ai/behavior-ai-service.d.ts +65 -0
  3. package/dist/ai/behavior-ai-service.d.ts.map +1 -0
  4. package/dist/ai/behavior-ai-service.js +205 -0
  5. package/dist/ai/behavior-ai-service.js.map +1 -0
  6. package/dist/ai/index.d.ts +27 -0
  7. package/dist/ai/index.d.ts.map +1 -1
  8. package/dist/ai/index.js +30 -0
  9. package/dist/ai/index.js.map +1 -1
  10. package/dist/ai/prompt-loader.js +2 -2
  11. package/dist/inference/quint-transpiler.d.ts.map +1 -1
  12. package/dist/inference/quint-transpiler.js +204 -4
  13. package/dist/inference/quint-transpiler.js.map +1 -1
  14. package/dist/libs/instance-factories/applications/templates/generic/backend-package-json-generator.js +4 -1
  15. package/dist/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.js +2 -2
  16. package/dist/libs/instance-factories/applications/templates/react/runtime-package-json-generator.js +1 -0
  17. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +97 -22
  18. package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +31 -31
  19. package/dist/libs/instance-factories/communication/templates/eventemitter/types-generator.js +79 -0
  20. package/dist/libs/instance-factories/communication/templates/eventemitter/websocket-bridge-generator.js +96 -0
  21. package/dist/libs/instance-factories/controllers/templates/fastify/routes-generator.js +45 -9
  22. package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +20 -2
  23. package/dist/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.js +10 -2
  24. package/dist/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.js +249 -0
  25. package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +72 -45
  26. package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +61 -54
  27. package/dist/libs/instance-factories/services/templates/prisma/service-generator.js +31 -10
  28. package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +101 -84
  29. package/dist/libs/instance-factories/views/templates/react/components-generator.js +40 -10
  30. package/dist/realize/index.d.ts.map +1 -1
  31. package/dist/realize/index.js +192 -23
  32. package/dist/realize/index.js.map +1 -1
  33. package/libs/instance-factories/applications/templates/generic/backend-package-json-generator.ts +4 -1
  34. package/libs/instance-factories/applications/templates/generic/backend-tsconfig-generator.ts +2 -2
  35. package/libs/instance-factories/applications/templates/react/runtime-package-json-generator.ts +6 -1
  36. package/libs/instance-factories/cli/templates/commander/command-generator.ts +115 -22
  37. package/libs/instance-factories/communication/event-emitter.yaml +16 -12
  38. package/libs/instance-factories/communication/templates/eventemitter/bus-generator.ts +33 -36
  39. package/libs/instance-factories/communication/templates/eventemitter/types-generator.ts +95 -0
  40. package/libs/instance-factories/communication/templates/eventemitter/websocket-bridge-generator.ts +105 -0
  41. package/libs/instance-factories/controllers/templates/fastify/routes-generator.ts +57 -11
  42. package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +23 -2
  43. package/libs/instance-factories/scaffolding/templates/generic/tsconfig-generator.ts +23 -2
  44. package/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.ts +376 -0
  45. package/libs/instance-factories/services/templates/prisma/behavior-generator.ts +116 -45
  46. package/libs/instance-factories/services/templates/prisma/controller-generator.ts +83 -59
  47. package/libs/instance-factories/services/templates/prisma/service-generator.ts +40 -10
  48. package/libs/instance-factories/services/templates/prisma/step-conventions.ts +169 -85
  49. package/libs/instance-factories/views/templates/react/components-generator.ts +50 -10
  50. package/package.json +1 -1
  51. package/dist/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.js +0 -232
  52. package/dist/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.js +0 -49
  53. package/dist/libs/instance-factories/tools/templates/mcp/static/src/index.js +0 -18
  54. package/dist/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.js +0 -0
  55. package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.js +0 -97
  56. package/dist/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.js +0 -64
  57. package/dist/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.js +0 -182
  58. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.js +0 -1210
  59. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.js +0 -172
  60. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.js +0 -240
  61. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.js +0 -147
  62. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.js +0 -281
  63. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.js +0 -409
  64. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.js +0 -414
  65. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.js +0 -467
  66. package/dist/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.js +0 -135
  67. package/dist/libs/instance-factories/tools/templates/mcp/static/src/types/index.js +0 -0
  68. package/dist/libs/instance-factories/tools/templates/vscode/static/extension.js +0 -965
@@ -24,13 +24,28 @@ export interface BehaviorContext {
24
24
  serviceName: string;
25
25
  operationName: string;
26
26
  prismaModel?: string;
27
+ /** Parameter names from the action definition (e.g., ['pollId', 'optionId', 'voterName']) */
28
+ parameterNames?: string[];
27
29
  }
28
30
 
31
+ /**
32
+ * A step can be a plain string or an object with extra metadata.
33
+ * String form: "Find Order by id"
34
+ * Object form: { step: "Apply discount", as: "discount", returns: { amount: "number" } }
35
+ */
36
+ export type Step = string | {
37
+ step: string;
38
+ /** Name the result variable (default: stepNResult) */
39
+ as?: string;
40
+ /** Declare return type for AI functions so types flow through */
41
+ returns?: Record<string, string> | string;
42
+ };
43
+
29
44
  export interface BehaviorMetadata {
30
45
  preconditions?: string[];
31
46
  postconditions?: string[];
32
47
  sideEffects?: string[];
33
- steps?: string[];
48
+ steps?: Step[];
34
49
  transactional?: boolean;
35
50
  }
36
51
 
@@ -43,6 +58,18 @@ export interface OperationMetadata {
43
58
  export interface BehaviorResult {
44
59
  body: string;
45
60
  helperMethods: string[];
61
+ /** Steps that couldn't be matched by conventions — need AI generation */
62
+ unmatchedSteps: Array<{
63
+ step: string;
64
+ functionName: string;
65
+ operationName: string;
66
+ /** Names of variables passed as the typed input object */
67
+ inputs: string[];
68
+ /** Declared return type from spec ({field: type} or a single type string) */
69
+ returns?: Record<string, string> | string;
70
+ /** Named result variable (default: stepNResult) */
71
+ resultName?: string;
72
+ }>;
46
73
  }
47
74
 
48
75
  /**
@@ -71,15 +98,18 @@ export function generateBehaviorWithHelpers(
71
98
  const parts: string[] = [];
72
99
  const helperMethods: string[] = [];
73
100
 
101
+ // Track variables declared by preconditions so steps can reuse them
102
+ const preconditionDeclared = new Set<string>();
103
+
74
104
  // Precondition guards
75
105
  const preconditions = generatePreconditionChecks(
76
- behavior.preconditions || [], context
106
+ behavior.preconditions || [], context, preconditionDeclared
77
107
  );
78
108
  if (preconditions) parts.push(preconditions);
79
109
 
80
110
  // Main logic (from steps or inferred from operation semantics)
81
- const { code, helpers } = generateStepLogic(
82
- behavior.steps || [], context
111
+ const { code, helpers, unmatchedSteps } = generateStepLogic(
112
+ behavior.steps || [], context, preconditionDeclared
83
113
  );
84
114
  parts.push(code);
85
115
  helperMethods.push(...helpers);
@@ -92,7 +122,7 @@ export function generateBehaviorWithHelpers(
92
122
 
93
123
  // Event publishing
94
124
  const events = generateEventPublishing(
95
- behavior.sideEffects || [], context.operationName
125
+ behavior.sideEffects || [], context.operationName, context.parameterNames
96
126
  );
97
127
  if (events) parts.push(events);
98
128
 
@@ -103,39 +133,56 @@ export function generateBehaviorWithHelpers(
103
133
  body = generateTransactionWrapper(body, context);
104
134
  }
105
135
 
106
- return { body, helperMethods };
136
+ return { body, helperMethods, unmatchedSteps };
107
137
  }
108
138
 
109
139
  /**
110
140
  * Generate precondition checks from natural-language strings.
141
+ * Tracks declared variables to avoid duplicates and reuses fetched entities.
111
142
  */
112
143
  export function generatePreconditionChecks(
113
144
  preconditions: string[],
114
- context: BehaviorContext
145
+ context: BehaviorContext,
146
+ declared?: Set<string>
115
147
  ): string {
116
148
  if (preconditions.length === 0) return '';
117
149
 
118
- const checks = preconditions.map(pc => matchPreconditionPattern(pc, context));
150
+ if (!declared) declared = new Set<string>();
151
+ const checks = preconditions.map(pc => matchPreconditionPattern(pc, context, declared));
119
152
  return ` // === PRECONDITIONS ===\n${checks.join('\n')}`;
120
153
  }
121
154
 
155
+ /** Find the parameter name that refers to a model (e.g., 'pollId' for 'Poll') */
156
+ function findIdParam(modelName: string, paramNames: string[]): string {
157
+ const modelVar = modelName.charAt(0).toLowerCase() + modelName.slice(1);
158
+ // Check for modelId (e.g., pollId)
159
+ const idParam = paramNames.find(p => p === `${modelVar}Id`);
160
+ if (idParam) return idParam;
161
+ // Check for just 'id'
162
+ if (paramNames.includes('id')) return 'id';
163
+ return `${modelVar}Id`;
164
+ }
165
+
122
166
  function matchPreconditionPattern(
123
167
  precondition: string,
124
- context: BehaviorContext
168
+ context: BehaviorContext,
169
+ declared: Set<string>
125
170
  ): string {
126
171
  const pc = precondition.toLowerCase();
127
- const prismaModel = context.prismaModel || context.modelName;
172
+ const params = context.parameterNames || [];
128
173
 
129
174
  // Pattern: "{Model} exists"
130
175
  const existsMatch = precondition.match(/^(\w+)\s+exists/i);
131
176
  if (existsMatch) {
132
177
  const entity = existsMatch[1];
133
178
  const entityVar = entity.charAt(0).toLowerCase() + entity.slice(1);
134
- return ` // Guard: ${precondition}
135
- const ${entityVar} = await prisma.${entityVar}.findUnique({ where: { id: params.id } });
136
- if (!${entityVar}) {
137
- throw new Error('Precondition failed: ${precondition}');
138
- }`;
179
+ const idParam = findIdParam(entity, params);
180
+ if (declared.has(entityVar)) {
181
+ // Already fetched — just check it
182
+ return ` // Guard: ${precondition} (already fetched)`;
183
+ }
184
+ declared.add(entityVar);
185
+ return ` const ${entityVar} = await prisma.${entityVar}.findUniqueOrThrow({ where: { id: ${idParam} } });`;
139
186
  }
140
187
 
141
188
  // Pattern: "{field} is not empty" / "{field} is required"
@@ -143,31 +190,22 @@ function matchPreconditionPattern(
143
190
  const fieldMatch = precondition.match(/^(\w+)\s+is/i);
144
191
  if (fieldMatch) {
145
192
  const field = fieldMatch[1];
146
- return ` // Guard: ${precondition}
147
- if (!params.${field}) {
148
- throw new Error('Precondition failed: ${precondition}');
149
- }`;
193
+ const paramRef = params.includes(field) ? field : `params.${field}`;
194
+ return ` if (!${paramRef}) throw new Error('${field} is required');`;
150
195
  }
151
196
  }
152
197
 
153
198
  // Pattern: "{field} is valid"
154
199
  if (pc.includes('is valid')) {
155
- return ` // Guard: ${precondition}
156
- const validation = this.validate(params, { operation: '${context.operationName}' });
157
- if (!validation.valid) {
158
- throw new Error('Precondition failed: ${precondition} — ' + validation.errors.join(', '));
159
- }`;
200
+ return ` // TODO: Implement validation: ${precondition}`;
160
201
  }
161
202
 
162
203
  // Pattern: "{X} matches {Y}" / "{X} equals {Y}"
163
204
  const matchesMatch = precondition.match(/^(\w+)\s+(?:matches|equals)\s+(.+)/i);
164
205
  if (matchesMatch) {
165
- const left = matchesMatch[1];
166
- const right = matchesMatch[2];
167
- return ` // Guard: ${precondition}
168
- if (params.${left.charAt(0).toLowerCase() + left.slice(1)} !== params.${right.charAt(0).toLowerCase() + right.slice(1)}) {
169
- throw new Error('Precondition failed: ${precondition}');
170
- }`;
206
+ const left = matchesMatch[1].charAt(0).toLowerCase() + matchesMatch[1].slice(1);
207
+ const right = matchesMatch[2].charAt(0).toLowerCase() + matchesMatch[2].slice(1);
208
+ return ` if (${left} !== ${right}) throw new Error('${matchesMatch[1]} must match ${matchesMatch[2]}');`;
171
209
  }
172
210
 
173
211
  // Pattern: "{Model} is {state}"
@@ -176,30 +214,43 @@ function matchPreconditionPattern(
176
214
  const model = stateMatch[1];
177
215
  const state = stateMatch[2];
178
216
  const modelVar = model.charAt(0).toLowerCase() + model.slice(1);
179
- return ` // Guard: ${precondition}
180
- const ${modelVar}State = await prisma.${modelVar}.findUniqueOrThrow({ where: { id: params.id } });
181
- if (${modelVar}State.status !== '${state}') {
182
- throw new Error('Precondition failed: ${precondition} (current: ' + ${modelVar}State.status + ')');
183
- }`;
217
+ const idParam = findIdParam(model, params);
218
+
219
+ // If already declared, reuse; otherwise fetch
220
+ if (!declared.has(modelVar)) {
221
+ declared.add(modelVar);
222
+ return ` const ${modelVar} = await prisma.${modelVar}.findUniqueOrThrow({ where: { id: ${idParam} } });
223
+ if (${modelVar}.status !== '${state}') throw new Error('${model} must be ${state}, got ' + ${modelVar}.status);`;
224
+ }
225
+ return ` if (${modelVar}.status !== '${state}') throw new Error('${model} must be ${state}, got ' + ${modelVar}.status);`;
184
226
  }
185
227
 
186
228
  // Unrecognized
187
- return ` // Guard: ${precondition}
188
- // TODO: Implement precondition check`;
229
+ return ` // TODO: Implement precondition: ${precondition}`;
189
230
  }
190
231
 
191
232
  /**
192
233
  * Generate step logic using convention-based pattern matching.
193
234
  */
194
235
  function generateStepLogic(
195
- steps: string[],
196
- context: BehaviorContext
197
- ): { code: string; helpers: string[] } {
236
+ steps: Step[],
237
+ context: BehaviorContext,
238
+ preconditionDeclared?: Set<string>
239
+ ): { code: string; helpers: string[]; unmatchedSteps: BehaviorResult['unmatchedSteps'] } {
198
240
  const helpers: string[] = [];
241
+ const unmatchedSteps: BehaviorResult['unmatchedSteps'] = [];
242
+
243
+ // Shared declared variables across steps — carried through from preconditions
244
+ const declaredVars = preconditionDeclared || new Set<string>();
199
245
 
200
246
  if (steps && steps.length > 0) {
201
- const stepCode = steps.map((step, i) => {
202
- if (typeof step !== 'string') {
247
+ const stepCode = steps.map((stepInput, i) => {
248
+ // Normalize to { text, as, returns }
249
+ const stepText = typeof stepInput === 'string' ? stepInput : stepInput.step;
250
+ const resultName = typeof stepInput === 'object' ? stepInput.as : undefined;
251
+ const returns = typeof stepInput === 'object' ? stepInput.returns : undefined;
252
+
253
+ if (typeof stepText !== 'string') {
203
254
  return ` // Step ${i + 1}: Complex operation — see expanded definition`;
204
255
  }
205
256
 
@@ -209,18 +260,32 @@ function generateStepLogic(
209
260
  serviceName: context.serviceName,
210
261
  operationName: context.operationName,
211
262
  stepNum: i + 1,
263
+ parameterNames: context.parameterNames,
264
+ declaredVars,
265
+ resultName, // Pass through the named result
212
266
  };
213
267
 
214
- const result = matchStep(step, ctx);
268
+ const result = matchStep(stepText, ctx);
215
269
  if (result.helperMethod) {
216
270
  helpers.push(result.helperMethod);
217
271
  }
272
+ if (!result.matched && result.functionName) {
273
+ unmatchedSteps.push({
274
+ step: stepText,
275
+ functionName: result.functionName,
276
+ operationName: context.operationName,
277
+ inputs: result.inputs || [],
278
+ returns,
279
+ resultName: resultName || `step${i + 1}Result`,
280
+ });
281
+ }
218
282
  return result.call;
219
283
  });
220
284
 
221
285
  return {
222
286
  code: ` // === EXECUTE ===\n${stepCode.join('\n\n')}`,
223
287
  helpers,
288
+ unmatchedSteps,
224
289
  };
225
290
  }
226
291
 
@@ -228,6 +293,7 @@ function generateStepLogic(
228
293
  return {
229
294
  code: ` // === EXECUTE ===\n${inferLogicFromOperationName(context)}`,
230
295
  helpers,
296
+ unmatchedSteps,
231
297
  };
232
298
  }
233
299
 
@@ -280,12 +346,17 @@ ${checks.join('\n')}
280
346
  */
281
347
  export function generateEventPublishing(
282
348
  sideEffects: string[],
283
- operationName: string
349
+ operationName: string,
350
+ parameterNames?: string[]
284
351
  ): string {
285
352
  if (!sideEffects || sideEffects.length === 0) return '';
286
353
 
354
+ const paramFields = parameterNames?.length
355
+ ? parameterNames.join(', ') + ', '
356
+ : '';
357
+
287
358
  const publishes = sideEffects.map(event =>
288
- ` this.emit('${event}', { operation: '${operationName}', timestamp: new Date().toISOString() });`
359
+ ` await eventBus.publish('${event}', { ${paramFields}timestamp: new Date().toISOString() } as any);`
289
360
  );
290
361
 
291
362
  return ` // === EVENTS ===\n${publishes.join('\n')}`;
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { TemplateContext } from '@specverse/types';
9
9
  import { buildTransitionMap, isAutoField } from '@specverse/types/spec-rules';
10
+ import { generateBehaviorWithHelpers, type BehaviorContext, type BehaviorMetadata } from './behavior-generator.js';
10
11
 
11
12
  /**
12
13
  * Generate Prisma controller for a model
@@ -25,9 +26,15 @@ export default function generatePrismaController(context: TemplateContext): stri
25
26
  const controllerName = controller.name;
26
27
  const modelName = model.name;
27
28
  const rawModelVar = modelName.charAt(0).toLowerCase() + modelName.slice(1);
28
- // Avoid JavaScript reserved words as variable names
29
+ // Avoid JavaScript reserved words as local variable names
29
30
  const RESERVED_WORDS = new Set(['import', 'export', 'default', 'class', 'function', 'return', 'delete', 'new', 'this', 'switch', 'case', 'break', 'continue', 'for', 'while', 'do', 'if', 'else', 'try', 'catch', 'finally', 'throw', 'typeof', 'instanceof', 'in', 'of', 'let', 'const', 'var', 'void', 'with', 'yield', 'async', 'await', 'enum', 'implements', 'interface', 'package', 'private', 'protected', 'public', 'static', 'super', 'extends']);
30
31
  const modelVar = RESERVED_WORDS.has(rawModelVar) ? `${rawModelVar}Item` : rawModelVar;
32
+ // Prisma delegate name always matches the Prisma model's camelCase form —
33
+ // even when the model name is a JS reserved word (prisma.import, prisma.export).
34
+ // Access these via bracket notation so tsc doesn't choke on the keyword.
35
+ const prismaDelegate = RESERVED_WORDS.has(rawModelVar)
36
+ ? `prisma['${rawModelVar}']`
37
+ : `prisma.${rawModelVar}`;
31
38
  const curedOps = controller.cured || {};
32
39
 
33
40
  // Determine ID type for proper parsing
@@ -36,6 +43,9 @@ export default function generatePrismaController(context: TemplateContext): stri
36
43
  const idType = idAttr?.type || 'UUID';
37
44
  const needsIntParse = idType === 'Integer' || idType === 'Int' || idType === 'Number';
38
45
 
46
+ // Generate custom actions and collect AI behavior stubs
47
+ const customActions = generateCustomActions(controller, modelName, modelVar);
48
+
39
49
  return `/**
40
50
  * ${controllerName}
41
51
  * Model-specific business logic for ${modelName}
@@ -43,7 +53,8 @@ export default function generatePrismaController(context: TemplateContext): stri
43
53
  */
44
54
 
45
55
  import { PrismaClient } from '@prisma/client';
46
- ${hasEventPublishing(curedOps, controller) ? `import { eventBus, EventName } from '../events/eventBus.js';` : ''}
56
+ ${hasEventPublishing(curedOps, controller) ? `import { eventBus } from '../events/eventBus.js';` : ''}
57
+ ${customActions.needsAiBehaviors ? `import * as aiBehaviors from '../behaviors/${modelName}Controller.ai.js';` : ''}
47
58
 
48
59
  const prisma = new PrismaClient();
49
60
 
@@ -57,18 +68,19 @@ function parseId(id: string): ${needsIntParse ? 'number' : 'string'} {
57
68
  */
58
69
  export class ${controllerName} {
59
70
  ${generateValidateMethod(model, modelName)}
60
- ${curedOps.create ? generateCreateMethod(model, modelName, modelVar, controller, allModels) : ''}
61
- ${curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar) : ''}
62
- ${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, controller, allModels) : ''}
63
- ${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, controller) : ''}
64
- ${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, controller) : ''}
65
- ${generateCustomActions(controller, modelName, modelVar)}
71
+ ${curedOps.create ? generateCreateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ''}
72
+ ${curedOps.retrieve ? generateRetrieveMethod(model, modelName, modelVar, prismaDelegate) : ''}
73
+ ${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, prismaDelegate, controller, allModels) : ''}
74
+ ${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, prismaDelegate, controller) : ''}
75
+ ${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, prismaDelegate, controller) : ''}
76
+ ${customActions.code}
66
77
  }
67
78
 
68
79
  // Export singleton instance
69
80
  export const ${modelVar}Controller = new ${controllerName}();
70
81
  export default ${modelVar}Controller;
71
82
  `;
83
+
72
84
  }
73
85
 
74
86
  /**
@@ -157,10 +169,7 @@ function generateValidationLogic(model: any, dataParam: string = '_data', contex
157
169
  /**
158
170
  * Generate create method
159
171
  */
160
- function generateCreateMethod(model: any, modelName: string, modelVar: string, controller: any, allModels?: any[]): string {
161
- const hasEvents = controller.publishes && Array.isArray(controller.publishes);
162
- const createEvent = hasEvents ? controller.publishes.find((e: string) => e.includes('Created')) : null;
163
-
172
+ function generateCreateMethod(model: any, modelName: string, modelVar: string, prismaDelegate: string, controller: any, allModels?: any[]): string {
164
173
  return `
165
174
  /**
166
175
  * Create a new ${modelName}
@@ -177,17 +186,12 @@ function generateCreateMethod(model: any, modelName: string, modelVar: string, c
177
186
  ${generateFKTransform(model, 'prismaData', allModels)}
178
187
 
179
188
  // Create record
180
- const ${modelVar} = await prisma.${modelVar}.create({
189
+ const ${modelVar} = await ${prismaDelegate}.create({
181
190
  data: prismaData${generateIncludeRelationships(model)}
182
191
  });
183
192
 
184
- ${createEvent ? `
185
- // Publish event
186
- eventBus.publish(EventName.${createEvent}, {
187
- ${modelVar},
188
- timestamp: new Date().toISOString()
189
- });
190
- ` : ''}
193
+ // Publish CURED event
194
+ await eventBus.publish('${modelName}Created', { ...${modelVar}, timestamp: new Date().toISOString() } as any);
191
195
 
192
196
  return ${modelVar};
193
197
  }
@@ -197,13 +201,13 @@ function generateCreateMethod(model: any, modelName: string, modelVar: string, c
197
201
  /**
198
202
  * Generate retrieve method
199
203
  */
200
- function generateRetrieveMethod(model: any, modelName: string, modelVar: string): string {
204
+ function generateRetrieveMethod(model: any, modelName: string, modelVar: string, prismaDelegate: string): string {
201
205
  return `
202
206
  /**
203
207
  * Retrieve ${modelName} by ID
204
208
  */
205
209
  public async retrieve(id: string): Promise<any> {
206
- const ${modelVar} = await prisma.${modelVar}.findUnique({
210
+ const ${modelVar} = await ${prismaDelegate}.findUnique({
207
211
  where: { id: parseId(id) }${generateIncludeRelationships(model)}
208
212
  });
209
213
 
@@ -218,7 +222,7 @@ function generateRetrieveMethod(model: any, modelName: string, modelVar: string)
218
222
  * Retrieve all ${modelName}s
219
223
  */
220
224
  public async retrieveAll(options: { skip?: number; take?: number } = {}): Promise<any[]> {
221
- return await prisma.${modelVar}.findMany({
225
+ return await ${prismaDelegate}.findMany({
222
226
  skip: options.skip,
223
227
  take: options.take${generateIncludeRelationships(model)}
224
228
  });
@@ -229,10 +233,7 @@ function generateRetrieveMethod(model: any, modelName: string, modelVar: string)
229
233
  /**
230
234
  * Generate update method
231
235
  */
232
- function generateUpdateMethod(model: any, modelName: string, modelVar: string, controller: any, allModels?: any[]): string {
233
- const hasEvents = controller.publishes && Array.isArray(controller.publishes);
234
- const updateEvent = hasEvents ? controller.publishes.find((e: string) => e.includes('Updated')) : null;
235
-
236
+ function generateUpdateMethod(model: any, modelName: string, modelVar: string, prismaDelegate: string, controller: any, allModels?: any[]): string {
236
237
  return `
237
238
  /**
238
239
  * Update ${modelName}
@@ -257,18 +258,13 @@ function generateUpdateMethod(model: any, modelName: string, modelVar: string, c
257
258
  ${generateFKTransform(model, 'updateData', allModels)}
258
259
 
259
260
  // Update record
260
- const ${modelVar} = await prisma.${modelVar}.update({
261
+ const ${modelVar} = await ${prismaDelegate}.update({
261
262
  where: { id: parseId(id) },
262
263
  data: updateData${generateIncludeRelationships(model)}
263
264
  });
264
265
 
265
- ${updateEvent ? `
266
- // Publish event
267
- eventBus.publish(EventName.${updateEvent}, {
268
- ${modelVar},
269
- timestamp: new Date().toISOString()
270
- });
271
- ` : ''}
266
+ // Publish CURED event
267
+ await eventBus.publish('${modelName}Updated', { ...${modelVar}, timestamp: new Date().toISOString() } as any);
272
268
 
273
269
  return ${modelVar};
274
270
  }
@@ -278,7 +274,7 @@ function generateUpdateMethod(model: any, modelName: string, modelVar: string, c
278
274
  /**
279
275
  * Generate evolve method (lifecycle-aware updates)
280
276
  */
281
- function generateEvolveMethod(model: any, modelName: string, modelVar: string, controller: any): string {
277
+ function generateEvolveMethod(model: any, modelName: string, modelVar: string, prismaDelegate: string, controller: any): string {
282
278
  // Extract lifecycle — handle both array and object format
283
279
  const lifecycles = Array.isArray(model.lifecycles) ? model.lifecycles :
284
280
  (model.lifecycles ? Object.entries(model.lifecycles).map(([name, lc]: [string, any]) => ({ name, ...lc })) : []);
@@ -302,7 +298,7 @@ function generateEvolveMethod(model: any, modelName: string, modelVar: string, c
302
298
  }
303
299
 
304
300
  // Get current record to check lifecycle state
305
- const current = await prisma.${modelVar}.findUnique({ where: { id: parseId(id) } });
301
+ const current = await ${prismaDelegate}.findUnique({ where: { id: parseId(id) } });
306
302
  if (!current) {
307
303
  throw new Error('${modelName} not found');
308
304
  }
@@ -321,11 +317,14 @@ function generateEvolveMethod(model: any, modelName: string, modelVar: string, c
321
317
  ` : ''}
322
318
 
323
319
  // Update record
324
- const ${modelVar} = await prisma.${modelVar}.update({
320
+ const ${modelVar} = await ${prismaDelegate}.update({
325
321
  where: { id: parseId(id) },
326
322
  data${generateIncludeRelationships(model)}
327
323
  });
328
324
 
325
+ // Publish CURED event
326
+ await eventBus.publish('${modelName}Evolved', { ...${modelVar}, timestamp: new Date().toISOString() } as any);
327
+
329
328
  return ${modelVar};
330
329
  }
331
330
  `;
@@ -334,33 +333,23 @@ function generateEvolveMethod(model: any, modelName: string, modelVar: string, c
334
333
  /**
335
334
  * Generate delete method
336
335
  */
337
- function generateDeleteMethod(model: any, modelName: string, modelVar: string, controller: any): string {
338
- const hasEvents = controller.publishes && Array.isArray(controller.publishes);
339
- const deleteEvent = hasEvents ? controller.publishes.find((e: string) => e.includes('Deleted')) : null;
340
-
336
+ function generateDeleteMethod(model: any, modelName: string, modelVar: string, prismaDelegate: string, controller: any): string {
341
337
  return `
342
338
  /**
343
339
  * Delete ${modelName}
344
340
  */
345
341
  public async delete(id: string): Promise<void> {
346
- ${deleteEvent ? `
347
342
  // Get record before deletion for event
348
- const ${modelVar} = await prisma.${modelVar}.findUnique({ where: { id: parseId(id) } });
349
- ` : ''}
343
+ const ${modelVar} = await ${prismaDelegate}.findUnique({ where: { id: parseId(id) } });
350
344
 
351
- await prisma.${modelVar}.delete({
345
+ await ${prismaDelegate}.delete({
352
346
  where: { id: parseId(id) }
353
347
  });
354
348
 
355
- ${deleteEvent ? `
356
- // Publish event
349
+ // Publish CURED event
357
350
  if (${modelVar}) {
358
- eventBus.publish(EventName.${deleteEvent}, {
359
- ${modelVar},
360
- timestamp: new Date().toISOString()
361
- });
351
+ await eventBus.publish('${modelName}Deleted', { ...${modelVar}, timestamp: new Date().toISOString() } as any);
362
352
  }
363
- ` : ''}
364
353
  }
365
354
  `;
366
355
  }
@@ -368,26 +357,59 @@ function generateDeleteMethod(model: any, modelName: string, modelVar: string, c
368
357
  /**
369
358
  * Generate custom actions defined in controller
370
359
  */
371
- function generateCustomActions(controller: any, modelName: string, modelVar: string): string {
360
+ interface CustomActionsResult {
361
+ code: string;
362
+ unmatchedSteps: Array<{ step: string; functionName: string; operationName: string }>;
363
+ needsAiBehaviors: boolean;
364
+ }
365
+
366
+ function generateCustomActions(controller: any, modelName: string, modelVar: string): CustomActionsResult {
372
367
  if (!controller.actions || Object.keys(controller.actions).length === 0) {
373
- return '';
368
+ return { code: '', unmatchedSteps: [], needsAiBehaviors: false };
374
369
  }
375
370
 
376
371
  const actions: string[] = [];
372
+ const allUnmatchedSteps: Array<{ step: string; functionName: string; operationName: string }> = [];
377
373
 
378
374
  Object.entries(controller.actions).forEach(([actionName, action]: [string, any]) => {
375
+ const behavior: BehaviorMetadata = {
376
+ preconditions: action.requires || action.preconditions || [],
377
+ steps: action.steps || [],
378
+ postconditions: action.ensures || action.postconditions || [],
379
+ sideEffects: action.publishes || action.events || [],
380
+ transactional: action.transactional,
381
+ };
382
+
383
+ const ctx: BehaviorContext = {
384
+ modelName,
385
+ serviceName: `${modelName}Controller`,
386
+ operationName: actionName,
387
+ prismaModel: modelVar,
388
+ parameterNames: Object.keys(action.parameters || {}),
389
+ };
390
+
391
+ const result = generateBehaviorWithHelpers(behavior, {}, ctx);
392
+ allUnmatchedSteps.push(...result.unmatchedSteps);
393
+
379
394
  actions.push(`
380
395
  /**
381
396
  * ${actionName}
382
397
  * ${action.description || ''}
383
398
  */
384
399
  public async ${actionName}(${generateActionParams(action)}): Promise<any> {
385
- // TODO: Implement ${actionName} logic
386
- throw new Error('${actionName} not implemented');
400
+ ${result.body}
387
401
  }`);
402
+
403
+ if (result.helperMethods.length > 0) {
404
+ actions.push(...result.helperMethods);
405
+ }
388
406
  });
389
407
 
390
- return actions.join('\n');
408
+ return {
409
+ code: actions.join('\n'),
410
+ unmatchedSteps: allUnmatchedSteps,
411
+ needsAiBehaviors: allUnmatchedSteps.length > 0,
412
+ };
391
413
  }
392
414
 
393
415
  /**
@@ -509,5 +531,7 @@ ${includes}
509
531
  * Check if controller publishes events
510
532
  */
511
533
  function hasEventPublishing(curedOps: any, controller: any): boolean {
512
- return controller.publishes && Array.isArray(controller.publishes) && controller.publishes.length > 0;
534
+ // Controllers always publish CURED events (Created, Updated, Deleted, Evolved)
535
+ // so the event bus is always needed
536
+ return true;
513
537
  }