@specverse/engines 4.1.12 → 4.1.14

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 (48) hide show
  1. package/assets/prompts/core/standard/v9/behavior.prompt.yaml +120 -0
  2. package/dist/ai/behavior-ai-service.d.ts +63 -0
  3. package/dist/ai/behavior-ai-service.d.ts.map +1 -0
  4. package/dist/ai/behavior-ai-service.js +203 -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/inference/index.d.ts.map +1 -1
  11. package/dist/inference/index.js +1 -0
  12. package/dist/inference/index.js.map +1 -1
  13. package/dist/inference/logical/logical-engine.d.ts.map +1 -1
  14. package/dist/inference/logical/logical-engine.js +3 -0
  15. package/dist/inference/logical/logical-engine.js.map +1 -1
  16. package/dist/libs/instance-factories/cli/templates/commander/cli-entry-generator.js +12 -11
  17. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +21 -5
  18. package/dist/libs/instance-factories/communication/templates/eventemitter/bus-generator.js +31 -30
  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 +25 -9
  22. package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +20 -2
  23. package/dist/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.js +141 -0
  24. package/dist/libs/instance-factories/services/templates/prisma/behavior-generator.js +62 -42
  25. package/dist/libs/instance-factories/services/templates/prisma/controller-generator.js +39 -7
  26. package/dist/libs/instance-factories/services/templates/prisma/step-conventions.js +101 -84
  27. package/dist/parser/processors/AttributeProcessor.d.ts.map +1 -1
  28. package/dist/parser/processors/AttributeProcessor.js +13 -6
  29. package/dist/parser/processors/AttributeProcessor.js.map +1 -1
  30. package/dist/parser/processors/ModelProcessor.d.ts.map +1 -1
  31. package/dist/parser/processors/ModelProcessor.js +7 -0
  32. package/dist/parser/processors/ModelProcessor.js.map +1 -1
  33. package/dist/realize/index.d.ts.map +1 -1
  34. package/dist/realize/index.js +54 -0
  35. package/dist/realize/index.js.map +1 -1
  36. package/libs/instance-factories/cli/templates/commander/cli-entry-generator.ts +12 -11
  37. package/libs/instance-factories/cli/templates/commander/command-generator.ts +21 -5
  38. package/libs/instance-factories/communication/event-emitter.yaml +16 -12
  39. package/libs/instance-factories/communication/templates/eventemitter/bus-generator.ts +33 -35
  40. package/libs/instance-factories/communication/templates/eventemitter/types-generator.ts +95 -0
  41. package/libs/instance-factories/communication/templates/eventemitter/websocket-bridge-generator.ts +105 -0
  42. package/libs/instance-factories/controllers/templates/fastify/routes-generator.ts +32 -11
  43. package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +23 -2
  44. package/libs/instance-factories/services/templates/prisma/ai-behaviors-generator.ts +211 -0
  45. package/libs/instance-factories/services/templates/prisma/behavior-generator.ts +86 -40
  46. package/libs/instance-factories/services/templates/prisma/controller-generator.ts +54 -8
  47. package/libs/instance-factories/services/templates/prisma/step-conventions.ts +166 -85
  48. package/package.json +1 -1
@@ -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
@@ -36,6 +37,9 @@ export default function generatePrismaController(context: TemplateContext): stri
36
37
  const idType = idAttr?.type || 'UUID';
37
38
  const needsIntParse = idType === 'Integer' || idType === 'Int' || idType === 'Number';
38
39
 
40
+ // Generate custom actions and collect AI behavior stubs
41
+ const customActions = generateCustomActions(controller, modelName, modelVar);
42
+
39
43
  return `/**
40
44
  * ${controllerName}
41
45
  * Model-specific business logic for ${modelName}
@@ -43,7 +47,8 @@ export default function generatePrismaController(context: TemplateContext): stri
43
47
  */
44
48
 
45
49
  import { PrismaClient } from '@prisma/client';
46
- ${hasEventPublishing(curedOps, controller) ? `import { eventBus, EventName } from '../events/eventBus.js';` : ''}
50
+ ${hasEventPublishing(curedOps, controller) ? `import { eventBus } from '../events/eventBus.js';` : ''}
51
+ ${customActions.needsAiBehaviors ? `import * as aiBehaviors from '../behaviors/${modelName}Controller.ai.js';` : ''}
47
52
 
48
53
  const prisma = new PrismaClient();
49
54
 
@@ -62,13 +67,14 @@ export class ${controllerName} {
62
67
  ${curedOps.update ? generateUpdateMethod(model, modelName, modelVar, controller, allModels) : ''}
63
68
  ${curedOps.evolve ? generateEvolveMethod(model, modelName, modelVar, controller) : ''}
64
69
  ${curedOps.delete ? generateDeleteMethod(model, modelName, modelVar, controller) : ''}
65
- ${generateCustomActions(controller, modelName, modelVar)}
70
+ ${customActions.code}
66
71
  }
67
72
 
68
73
  // Export singleton instance
69
74
  export const ${modelVar}Controller = new ${controllerName}();
70
75
  export default ${modelVar}Controller;
71
76
  `;
77
+
72
78
  }
73
79
 
74
80
  /**
@@ -368,26 +374,59 @@ function generateDeleteMethod(model: any, modelName: string, modelVar: string, c
368
374
  /**
369
375
  * Generate custom actions defined in controller
370
376
  */
371
- function generateCustomActions(controller: any, modelName: string, modelVar: string): string {
377
+ interface CustomActionsResult {
378
+ code: string;
379
+ unmatchedSteps: Array<{ step: string; functionName: string; operationName: string }>;
380
+ needsAiBehaviors: boolean;
381
+ }
382
+
383
+ function generateCustomActions(controller: any, modelName: string, modelVar: string): CustomActionsResult {
372
384
  if (!controller.actions || Object.keys(controller.actions).length === 0) {
373
- return '';
385
+ return { code: '', unmatchedSteps: [], needsAiBehaviors: false };
374
386
  }
375
387
 
376
388
  const actions: string[] = [];
389
+ const allUnmatchedSteps: Array<{ step: string; functionName: string; operationName: string }> = [];
377
390
 
378
391
  Object.entries(controller.actions).forEach(([actionName, action]: [string, any]) => {
392
+ const behavior: BehaviorMetadata = {
393
+ preconditions: action.requires || action.preconditions || [],
394
+ steps: action.steps || [],
395
+ postconditions: action.ensures || action.postconditions || [],
396
+ sideEffects: action.publishes || action.events || [],
397
+ transactional: action.transactional,
398
+ };
399
+
400
+ const ctx: BehaviorContext = {
401
+ modelName,
402
+ serviceName: `${modelName}Controller`,
403
+ operationName: actionName,
404
+ prismaModel: modelVar,
405
+ parameterNames: Object.keys(action.parameters || {}),
406
+ };
407
+
408
+ const result = generateBehaviorWithHelpers(behavior, {}, ctx);
409
+ allUnmatchedSteps.push(...result.unmatchedSteps);
410
+
379
411
  actions.push(`
380
412
  /**
381
413
  * ${actionName}
382
414
  * ${action.description || ''}
383
415
  */
384
416
  public async ${actionName}(${generateActionParams(action)}): Promise<any> {
385
- // TODO: Implement ${actionName} logic
386
- throw new Error('${actionName} not implemented');
417
+ ${result.body}
387
418
  }`);
419
+
420
+ if (result.helperMethods.length > 0) {
421
+ actions.push(...result.helperMethods);
422
+ }
388
423
  });
389
424
 
390
- return actions.join('\n');
425
+ return {
426
+ code: actions.join('\n'),
427
+ unmatchedSteps: allUnmatchedSteps,
428
+ needsAiBehaviors: allUnmatchedSteps.length > 0,
429
+ };
391
430
  }
392
431
 
393
432
  /**
@@ -509,5 +548,12 @@ ${includes}
509
548
  * Check if controller publishes events
510
549
  */
511
550
  function hasEventPublishing(curedOps: any, controller: any): boolean {
512
- return controller.publishes && Array.isArray(controller.publishes) && controller.publishes.length > 0;
551
+ if (controller.publishes && Array.isArray(controller.publishes) && controller.publishes.length > 0) return true;
552
+ // Check if any custom action publishes events
553
+ if (controller.actions) {
554
+ for (const action of Object.values(controller.actions) as any[]) {
555
+ if (action.publishes?.length > 0 || action.events?.length > 0 || action.sideEffects?.length > 0) return true;
556
+ }
557
+ }
558
+ return false;
513
559
  }
@@ -18,6 +18,10 @@ export interface StepContext {
18
18
  serviceName: string;
19
19
  operationName: string;
20
20
  stepNum: number;
21
+ /** Parameter names from the action (e.g., ['pollId', 'optionId']) */
22
+ parameterNames?: string[];
23
+ /** Variables already declared (to avoid redeclaration) */
24
+ declaredVars?: Set<string>;
21
25
  }
22
26
 
23
27
  function toVar(name: string): string {
@@ -28,15 +32,81 @@ function toMethod(words: string): string {
28
32
  return words.trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase()).replace(/^\w/, c => c.toLowerCase());
29
33
  }
30
34
 
35
+ /**
36
+ * Resolve a value expression in a step to a TypeScript expression.
37
+ *
38
+ * Handles:
39
+ * - "current time", "now", "timestamp" → new Date().toISOString()
40
+ * - References to declared variables (e.g., "discount.amount") → discount.amount
41
+ * - References to operation parameters → parameter name
42
+ * - References to stepNResult → stepNResult
43
+ * - Numbers → numeric literal
44
+ * - Boolean literals → true/false
45
+ * - Everything else → quoted string literal
46
+ */
47
+ function resolveValue(rawValue: string, ctx: StepContext): string {
48
+ const value = rawValue.trim().replace(/^['"]|['"]$/g, '');
49
+
50
+ // Time expressions
51
+ if (/^(current\s*time|now|timestamp)$/i.test(value)) {
52
+ return 'new Date().toISOString()';
53
+ }
54
+
55
+ // Boolean literals
56
+ if (value === 'true' || value === 'false') return value;
57
+
58
+ // Numeric literal
59
+ if (/^-?\d+(\.\d+)?$/.test(value)) return value;
60
+
61
+ const declared = ctx.declaredVars || new Set();
62
+ const params = ctx.parameterNames || [];
63
+
64
+ // Reference: "varName" or "varName.field" — check if root matches a declared var or parameter
65
+ const rootMatch = value.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(\.[a-zA-Z0-9_.]+)?$/);
66
+ if (rootMatch) {
67
+ const root = rootMatch[1];
68
+ if (declared.has(root) || params.includes(root)) {
69
+ return value; // use as-is (e.g., "discount.amount")
70
+ }
71
+ }
72
+
73
+ // Natural language reference: "calculated amount", "the discount" etc.
74
+ // Try to match to a declared var by tokenising
75
+ const tokens = value.toLowerCase().split(/\s+/);
76
+ for (const declaredName of [...declared, ...params]) {
77
+ const normalized = declaredName.toLowerCase();
78
+ if (tokens.includes(normalized)) return declaredName;
79
+ }
80
+
81
+ // "calculated X" / "computed X" / "the X" — look for a stepNResult with
82
+ // matching field. Since we don't know the AI function's return shape at
83
+ // compile time, emit a typed access with a TODO comment the developer can fix.
84
+ const calcMatch = value.match(/^(?:the\s+|calculated\s+|computed\s+|resulting\s+)(\w+)/i);
85
+ if (calcMatch) {
86
+ const field = calcMatch[1];
87
+ // Find most recent stepNResult
88
+ const stepResults = [...declared].filter(v => /^step\d+Result$/.test(v)).sort().reverse();
89
+ if (stepResults.length > 0) {
90
+ return `(${stepResults[0]} as any).${field} /* TODO: verify field name */`;
91
+ }
92
+ }
93
+
94
+ // Looks like natural language (multiple words with spaces) — emit TODO
95
+ if (/\s/.test(value)) {
96
+ return `/* TODO: resolve "${value}" — no matching declared variable */ null`;
97
+ }
98
+
99
+ // Fallback: quoted string literal
100
+ return `'${value.replace(/'/g, "\\'")}'`;
101
+ }
102
+
31
103
  export const STEP_CONVENTIONS: StepConvention[] = [
32
104
  // --- Validation ---
33
105
  {
34
106
  name: 'validate',
35
107
  pattern: /^validate\s+(.+)/i,
36
108
  generateCall: (m, ctx) => ` // Step ${ctx.stepNum}: Validate ${m[1]}
37
- const validationResult = this.validate(params, { operation: '${ctx.operationName}' });
38
- if (!validationResult.valid) {
39
- throw new Error(\`Validation failed: \${validationResult.errors.join(', ')}\`);
109
+ // TODO: Add validation logic for ${m[1]}
40
110
  }`,
41
111
  },
42
112
 
@@ -48,21 +118,9 @@ export const STEP_CONVENTIONS: StepConvention[] = [
48
118
  const condition = m[1];
49
119
  const methodName = toMethod('check ' + condition);
50
120
  return ` // Step ${ctx.stepNum}: Check ${condition}
51
- const checkResult = await this.${methodName}(params);
52
- if (!checkResult) {
53
- throw new Error('Check failed: ${condition}');
54
- }`;
55
- },
56
- generateMethod: (m, ctx) => {
57
- const condition = m[1];
58
- const methodName = toMethod('check ' + condition);
59
- return `
60
- private async ${methodName}(params: any): Promise<boolean> {
61
121
  // TODO: Implement check — ${condition}
62
- const ${toVar(ctx.prismaModel)} = await this.prisma.${toVar(ctx.prismaModel)}.findUnique({ where: { id: params.id } });
63
- if (!${toVar(ctx.prismaModel)}) return false;
64
- return true;
65
- }`;
122
+ // const checkResult = ...
123
+ // if (!checkResult) throw new Error('Check failed: ${condition}');`;
66
124
  },
67
125
  },
68
126
 
@@ -73,22 +131,40 @@ export const STEP_CONVENTIONS: StepConvention[] = [
73
131
  generateCall: (m, ctx) => {
74
132
  const model = m[1];
75
133
  const field = m[2];
134
+ const modelVar = toVar(model);
135
+ const params = ctx.parameterNames || [];
136
+ const declared = ctx.declaredVars || new Set();
137
+
138
+ // Find the ID parameter: modelId (e.g., pollId) or field directly
139
+ const idParam = field === 'id'
140
+ ? (params.find(p => p === `${modelVar}Id`) || params.find(p => p === 'id') || `${modelVar}Id`)
141
+ : (params.find(p => p === field) || `params.${field}`);
142
+
143
+ // Skip if already declared by precondition
144
+ if (declared.has(modelVar)) {
145
+ return ` // Step ${ctx.stepNum}: Find ${model} by ${field} (already loaded)`;
146
+ }
147
+ declared.add(modelVar);
148
+
76
149
  return ` // Step ${ctx.stepNum}: Find ${model} by ${field}
77
- const ${toVar(model)} = await this.prisma.${toVar(model)}.findUnique({ where: { ${field}: params.${field} } });
78
- if (!${toVar(model)}) {
79
- throw new Error('${model} not found by ${field}');
80
- }`;
150
+ const ${modelVar} = await prisma.${modelVar}.findUniqueOrThrow({ where: { ${field}: ${idParam} } });`;
81
151
  },
82
152
  },
83
153
 
84
154
  // --- Create ---
85
155
  {
86
156
  name: 'create',
87
- pattern: /^create\s+(\w+)(?:\s+record)?/i,
157
+ pattern: /^create\s+(\w+)(?:\s+(?:with\s+)?(.+))?/i,
88
158
  generateCall: (m, ctx) => {
89
159
  const model = m[1];
160
+ const modelVar = toVar(model);
161
+ // Build data from parameter names if available
162
+ const paramNames = ctx.parameterNames || [];
163
+ const dataFields = paramNames.length > 0
164
+ ? `{ ${paramNames.join(', ')} }`
165
+ : 'data';
90
166
  return ` // Step ${ctx.stepNum}: Create ${model}
91
- const ${toVar(model)} = await this.prisma.${toVar(model)}.create({ data: params });`;
167
+ const ${modelVar} = await prisma.${modelVar}.create({ data: ${dataFields} });`;
92
168
  },
93
169
  },
94
170
 
@@ -99,12 +175,12 @@ export const STEP_CONVENTIONS: StepConvention[] = [
99
175
  generateCall: (m, ctx) => {
100
176
  const model = m[1];
101
177
  const field = m[2];
102
- const value = m[3].trim().replace(/"/g, "'");
103
- const isString = /^[a-z]/.test(value) && !value.includes('.');
104
- const val = isString ? `'${value}'` : value;
105
- return ` // Step ${ctx.stepNum}: Update ${model} ${field} to ${value}
106
- await this.prisma.${toVar(model)}.update({
107
- where: { id: params.id },
178
+ const rawValue = m[3];
179
+ const modelVar = toVar(model);
180
+ const val = resolveValue(rawValue, ctx);
181
+ return ` // Step ${ctx.stepNum}: Update ${model} ${field} to ${rawValue.trim()}
182
+ await prisma.${modelVar}.update({
183
+ where: { id: ${modelVar}.id },
108
184
  data: { ${field}: ${val} },
109
185
  });`;
110
186
  },
@@ -116,9 +192,10 @@ export const STEP_CONVENTIONS: StepConvention[] = [
116
192
  pattern: /^update\s+(\w+)(?:\s+(.+))?/i,
117
193
  generateCall: (m, ctx) => {
118
194
  const model = m[1];
195
+ const modelVar = toVar(model);
119
196
  return ` // Step ${ctx.stepNum}: Update ${model}
120
- await this.prisma.${toVar(model)}.update({
121
- where: { id: params.id },
197
+ await prisma.${modelVar}.update({
198
+ where: { id: ${modelVar}.id },
122
199
  data: params,
123
200
  });`;
124
201
  },
@@ -130,8 +207,9 @@ export const STEP_CONVENTIONS: StepConvention[] = [
130
207
  pattern: /^delete\s+(\w+)/i,
131
208
  generateCall: (m, ctx) => {
132
209
  const model = m[1];
210
+ const modelVar = toVar(model);
133
211
  return ` // Step ${ctx.stepNum}: Delete ${model}
134
- await this.prisma.${toVar(model)}.delete({ where: { id: params.id } });`;
212
+ await prisma.${modelVar}.delete({ where: { id: ${modelVar}.id } });`;
135
213
  },
136
214
  },
137
215
 
@@ -142,11 +220,11 @@ export const STEP_CONVENTIONS: StepConvention[] = [
142
220
  generateCall: (m, ctx) => {
143
221
  const model = m[1];
144
222
  const state = m[2];
223
+ const modelVar = toVar(model);
145
224
  return ` // Step ${ctx.stepNum}: Transition ${model} to ${state}
146
- const current = await this.prisma.${toVar(model)}.findUniqueOrThrow({ where: { id: params.id } });
147
- // Lifecycle validation would check valid transition here
148
- await this.prisma.${toVar(model)}.update({
149
- where: { id: params.id },
225
+ if (${modelVar}.status === '${state}') throw new Error('${model} is already ${state}');
226
+ await prisma.${modelVar}.update({
227
+ where: { id: ${modelVar}.id },
150
228
  data: { status: '${state}' },
151
229
  });`;
152
230
  },
@@ -158,12 +236,12 @@ export const STEP_CONVENTIONS: StepConvention[] = [
158
236
  pattern: /^set\s+(\w+)\s+to\s+(.+)/i,
159
237
  generateCall: (m, ctx) => {
160
238
  const field = m[1];
161
- const value = m[2].trim();
162
- const isCurrentTime = /current time|now|timestamp/i.test(value);
163
- const val = isCurrentTime ? 'new Date()' : `'${value}'`;
164
- return ` // Step ${ctx.stepNum}: Set ${field} to ${value}
165
- await this.prisma.${toVar(ctx.prismaModel)}.update({
166
- where: { id: params.id },
239
+ const rawValue = m[2];
240
+ const modelVar = toVar(ctx.prismaModel);
241
+ const val = resolveValue(rawValue, ctx);
242
+ return ` // Step ${ctx.stepNum}: Set ${field} to ${rawValue.trim()}
243
+ await prisma.${modelVar}.update({
244
+ where: { id: ${modelVar}.id },
167
245
  data: { ${field}: ${val} },
168
246
  });`;
169
247
  },
@@ -176,10 +254,11 @@ export const STEP_CONVENTIONS: StepConvention[] = [
176
254
  generateCall: (m, ctx) => {
177
255
  const field = m[1];
178
256
  const amount = m[2];
179
- const amountVal = /^\d+$/.test(amount) ? amount : `params.${amount}`;
257
+ const modelVar = toVar(ctx.prismaModel);
258
+ const amountVal = /^\d+$/.test(amount) ? amount : amount;
180
259
  return ` // Step ${ctx.stepNum}: Increment ${field} by ${amount}
181
- await this.prisma.${toVar(ctx.prismaModel)}.update({
182
- where: { id: params.id },
260
+ await prisma.${modelVar}.update({
261
+ where: { id: ${modelVar}.id },
183
262
  data: { ${field}: { increment: ${amountVal} } },
184
263
  });`;
185
264
  },
@@ -192,39 +271,19 @@ export const STEP_CONVENTIONS: StepConvention[] = [
192
271
  generateCall: (m, ctx) => {
193
272
  const field = m[1];
194
273
  const amount = m[2];
195
- const amountVal = /^\d+$/.test(amount) ? amount : `params.${amount}`;
274
+ const modelVar = toVar(ctx.prismaModel);
275
+ const amountVal = /^\d+$/.test(amount) ? amount : amount;
196
276
  return ` // Step ${ctx.stepNum}: Decrement ${field} by ${amount}
197
- const current${field} = await this.prisma.${toVar(ctx.prismaModel)}.findUniqueOrThrow({ where: { id: params.id } });
198
- if ((current${field}.${field} || 0) < ${amountVal}) {
199
- throw new Error('Cannot decrement ${field} below zero');
200
- }
201
- await this.prisma.${toVar(ctx.prismaModel)}.update({
202
- where: { id: params.id },
277
+ await prisma.${modelVar}.update({
278
+ where: { id: ${modelVar}.id },
203
279
  data: { ${field}: { decrement: ${amountVal} } },
204
280
  });`;
205
281
  },
206
282
  },
207
283
 
208
- // --- Calculate ---
209
- {
210
- name: 'calculate',
211
- pattern: /^calculate\s+(.+)/i,
212
- generateCall: (m, ctx) => {
213
- const metric = m[1];
214
- const methodName = toMethod('calculate ' + metric);
215
- return ` // Step ${ctx.stepNum}: Calculate ${metric}
216
- const ${toVar(metric.replace(/\s+/g, ''))} = await this.${methodName}(params);`;
217
- },
218
- generateMethod: (m, ctx) => {
219
- const metric = m[1];
220
- const methodName = toMethod('calculate ' + metric);
221
- return `
222
- private async ${methodName}(params: any): Promise<number> {
223
- // TODO: Implement calculation — ${metric}
224
- return 0;
225
- }`;
226
- },
227
- },
284
+ // NOTE: "calculate X" is intentionally NOT a convention — it falls through to AI
285
+ // behaviors because calculations are domain-specific pure functions that benefit
286
+ // from AI generation with the full input context.
228
287
 
229
288
  // --- Send event ---
230
289
  {
@@ -233,7 +292,7 @@ export const STEP_CONVENTIONS: StepConvention[] = [
233
292
  generateCall: (m, ctx) => {
234
293
  const event = m[1];
235
294
  return ` // Step ${ctx.stepNum}: Emit ${event} event
236
- this.emit('${event}', { ${toVar(ctx.prismaModel)}Id: params.id, operation: '${ctx.operationName}', timestamp: new Date().toISOString() });`;
295
+ await eventBus.publish('${event}', { ${toVar(ctx.prismaModel)}Id: ${toVar(ctx.prismaModel)}.id, operation: '${ctx.operationName}', timestamp: new Date().toISOString() });`;
237
296
  },
238
297
  },
239
298
 
@@ -244,7 +303,7 @@ export const STEP_CONVENTIONS: StepConvention[] = [
244
303
  generateCall: (m, ctx) => {
245
304
  const type = m[1];
246
305
  return ` // Step ${ctx.stepNum}: Send ${type} notification
247
- this.emit('${type}Notification', { ${toVar(ctx.prismaModel)}Id: params.id, operation: '${ctx.operationName}' });`;
306
+ await eventBus.publish('${type}Notification', { ${toVar(ctx.prismaModel)}Id: ${toVar(ctx.prismaModel)}.id, operation: '${ctx.operationName}' });`;
248
307
  },
249
308
  },
250
309
 
@@ -256,7 +315,7 @@ export const STEP_CONVENTIONS: StepConvention[] = [
256
315
  const service = m[1];
257
316
  const method = m[2];
258
317
  return ` // Step ${ctx.stepNum}: Call ${service}.${method}
259
- await this.${toVar(service)}.${method}(params);`;
318
+ await ${toVar(service)}.${method}({ ${(ctx.parameterNames || []).join(', ')} });`;
260
319
  },
261
320
  },
262
321
 
@@ -280,30 +339,52 @@ export const STEP_CONVENTIONS: StepConvention[] = [
280
339
  /**
281
340
  * Match a step string against all conventions. Returns the generated call
282
341
  * code and optionally a helper method to add to the class.
342
+ *
343
+ * For unmatched steps, the call is wired to a pure AI function that takes
344
+ * all currently-declared variables as a typed input object and returns
345
+ * a result that subsequent steps can use.
283
346
  */
284
347
  export function matchStep(
285
348
  step: string,
286
349
  ctx: StepContext
287
- ): { call: string; helperMethod?: string } {
350
+ ): {
351
+ call: string;
352
+ helperMethod?: string;
353
+ matched: boolean;
354
+ functionName?: string;
355
+ inputs?: string[];
356
+ resultVar?: string;
357
+ } {
288
358
  for (const convention of STEP_CONVENTIONS) {
289
359
  const match = step.match(convention.pattern);
290
360
  if (match) {
291
361
  return {
292
362
  call: convention.generateCall(match, ctx),
293
363
  helperMethod: convention.generateMethod?.(match, ctx),
364
+ matched: true,
294
365
  };
295
366
  }
296
367
  }
297
368
 
298
- // No match — generate stub method
299
- const methodName = toMethod(step);
369
+ // No match — pure AI function call
370
+ // Inputs = all variables declared by previous steps + operation parameters
371
+ const functionName = toMethod(step);
372
+ const declared = Array.from(ctx.declaredVars || []);
373
+ const paramNames = ctx.parameterNames || [];
374
+ const inputs = [...paramNames, ...declared];
375
+
376
+ const resultVar = `step${ctx.stepNum}Result`;
377
+ const inputObj = inputs.length > 0 ? `{ ${inputs.join(', ')} }` : '{}';
378
+
379
+ // Register the result variable so subsequent steps can reference it
380
+ if (ctx.declaredVars) ctx.declaredVars.add(resultVar);
381
+
300
382
  return {
301
- call: ` // Step ${ctx.stepNum}: ${step}
302
- await this.${methodName}(params);`,
303
- helperMethod: `
304
- private async ${methodName}(params: any): Promise<void> {
305
- // TODO: Implement — ${step}
306
- throw new Error('Not implemented: ${methodName}');
307
- }`,
383
+ call: ` // Step ${ctx.stepNum}: ${step} [AI-generated — pure function]
384
+ const ${resultVar} = await aiBehaviors.${functionName}(${inputObj});`,
385
+ matched: false,
386
+ functionName,
387
+ inputs,
388
+ resultVar,
308
389
  };
309
390
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specverse/engines",
3
- "version": "4.1.12",
3
+ "version": "4.1.14",
4
4
  "description": "SpecVerse toolchain — parser, inference, realize, generators, AI, registry",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",