@specverse/engines 4.3.5 → 5.0.0
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.
- package/assets/examples/10-api/README.md +3 -3
- package/assets/prompts/core/README.md +1 -1
- package/dist/inference/core/rule-engine.d.ts +0 -12
- package/dist/inference/core/rule-engine.d.ts.map +1 -1
- package/dist/inference/core/rule-engine.js +99 -968
- package/dist/inference/core/rule-engine.js.map +1 -1
- package/dist/inference/core/template-helpers.d.ts +56 -0
- package/dist/inference/core/template-helpers.d.ts.map +1 -0
- package/dist/inference/core/template-helpers.js +87 -0
- package/dist/inference/core/template-helpers.js.map +1 -0
- package/dist/inference/logical/generators/service-generator.d.ts.map +1 -1
- package/dist/inference/logical/generators/service-generator.js +0 -4
- package/dist/inference/logical/generators/service-generator.js.map +1 -1
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +27 -5
- package/dist/libs/instance-factories/tools/README.md +1 -1
- package/dist/libs/instance-factories/tools/mcp.yaml +1 -1
- package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +336 -116
- package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +172 -8
- package/dist/libs/instance-factories/tools/vscode.yaml +1 -1
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +27 -5
- package/libs/instance-factories/tools/README.md +1 -1
- package/libs/instance-factories/tools/mcp.yaml +1 -1
- package/libs/instance-factories/tools/templates/mcp/mcp-server-generator.ts +386 -141
- package/libs/instance-factories/tools/templates/vscode/static/extension.ts +9 -2
- package/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.ts +246 -10
- package/libs/instance-factories/tools/vscode.yaml +1 -1
- package/package.json +5 -4
- package/libs/instance-factories/tools/templates/mcp/static/docs/DEPLOYMENT_GUIDE.md +0 -630
- package/libs/instance-factories/tools/templates/mcp/static/docs/HYBRID_RESOURCE_SYSTEM.md +0 -330
- package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/EXTENSION_DEPLOYMENT.md +0 -552
- package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/LOCAL_DEPLOYMENT.md +0 -164
- package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/WEB_DEPLOYMENT.md +0 -247
- package/libs/instance-factories/tools/templates/mcp/static/package.json +0 -94
- package/libs/instance-factories/tools/templates/mcp/static/scripts/build-enterprise.js +0 -284
- package/libs/instance-factories/tools/templates/mcp/static/scripts/build-extension.js +0 -139
- package/libs/instance-factories/tools/templates/mcp/static/scripts/build-local.js +0 -74
- package/libs/instance-factories/tools/templates/mcp/static/scripts/build-web.js +0 -156
- package/libs/instance-factories/tools/templates/mcp/static/scripts/copy-canonical-files.js +0 -41
- package/libs/instance-factories/tools/templates/mcp/static/scripts/test-deployments.js +0 -259
- package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-resources.js +0 -231
- package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-simple.js +0 -196
- package/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.ts +0 -293
- package/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.ts +0 -90
- package/libs/instance-factories/tools/templates/mcp/static/src/index.ts +0 -24
- package/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.ts +0 -15
- package/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.ts +0 -106
- package/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.ts +0 -75
- package/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.ts +0 -239
- package/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.ts +0 -1501
- package/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.ts +0 -211
- package/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.ts +0 -308
- package/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.ts +0 -210
- package/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.ts +0 -356
- package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.ts +0 -522
- package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.ts +0 -530
- package/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.ts +0 -594
- package/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.ts +0 -170
- package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.init.test.ts +0 -544
- package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.test.ts +0 -189
- package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/ResourcesProviderService.test.ts +0 -89
- package/libs/instance-factories/tools/templates/mcp/static/src/types/index.ts +0 -110
- package/libs/instance-factories/tools/templates/mcp/static/tsconfig.json +0 -28
- package/libs/instance-factories/tools/templates/vscode/static/schemas/specverse-v3-schema.json +0 -4279
- /package/libs/instance-factories/tools/templates/vscode/static/themes/{specverse-complete-theme.json → specverse-dark-theme.json} +0 -0
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
* Provides pattern matching and rule application for both logical and deployment inference
|
|
4
4
|
*/
|
|
5
5
|
import * as yaml from 'js-yaml';
|
|
6
|
+
import Handlebars from 'handlebars';
|
|
7
|
+
import { registerCoreHelpers } from './template-helpers.js';
|
|
8
|
+
// Register Handlebars helpers at module load (idempotent). Phase 1 of
|
|
9
|
+
// the template-engine rewrite — infrastructure lands here; templates
|
|
10
|
+
// continue to run through the old string-substitution apply*Template
|
|
11
|
+
// paths below until Phase 2 switches them over. Registering helpers
|
|
12
|
+
// early costs nothing and makes them available the moment the rewrite
|
|
13
|
+
// swaps in `Handlebars.compile(...)`.
|
|
14
|
+
// See docs/plans/2026-04-17-HANDLEBARS-TEMPLATE-ENGINE-REWRITE.md.
|
|
15
|
+
registerCoreHelpers();
|
|
6
16
|
export class RuleEngine {
|
|
7
17
|
debug;
|
|
8
18
|
rules = new Map();
|
|
@@ -357,101 +367,78 @@ export class RuleEngine {
|
|
|
357
367
|
};
|
|
358
368
|
}
|
|
359
369
|
applyHandlebarsTemplate(rule, templateContext) {
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
|
|
370
|
+
// Cycle 2: real Handlebars. Previously this method ran one-off
|
|
371
|
+
// hacks (hardcoded parentRelationships[0].targetModel substitution,
|
|
372
|
+
// blanket /each/ strip) because the home-rolled substitutor didn't
|
|
373
|
+
// understand `{{#each}}` or sub-expressions. Handlebars handles
|
|
374
|
+
// those natively — if multiple parentRelationships exist, the
|
|
375
|
+
// iteration emits one block per entry instead of just the first.
|
|
376
|
+
const template = Array.isArray(rule.template.content)
|
|
363
377
|
? rule.template.content.join('\n')
|
|
364
378
|
: rule.template.content;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
throw new Error(`Template content must be string or string array, got ${typeof result} in applyHandlebarsTemplate`);
|
|
368
|
-
}
|
|
369
|
-
// Simple variable substitution for common patterns
|
|
370
|
-
result = result.replace(/\{\{modelName\}\}/g, templateContext.modelName || '');
|
|
371
|
-
result = result.replace(/\{\{controllerName\}\}/g, templateContext.controllerName || '');
|
|
372
|
-
result = result.replace(/\{\{serviceName\}\}/g, templateContext.serviceName || '');
|
|
373
|
-
// Handle arrays and objects in a basic way
|
|
374
|
-
if (templateContext.parentRelationships?.length > 0) {
|
|
375
|
-
result = result.replace(/\{\{#each parentRelationships\}\}/g, '');
|
|
376
|
-
result = result.replace(/\{\{\/each\}\}/g, '');
|
|
377
|
-
result = result.replace(/\{\{targetModel\}\}/g, templateContext.parentRelationships[0].targetModel);
|
|
379
|
+
if (typeof template !== 'string') {
|
|
380
|
+
throw new Error(`Template content must be string or string array, got ${typeof template} in applyHandlebarsTemplate`);
|
|
378
381
|
}
|
|
382
|
+
const rendered = Handlebars.compile(template, { strict: false, noEscape: true })(templateContext);
|
|
383
|
+
// If rendered output looks like JSON, parse it; otherwise return
|
|
384
|
+
// as string. Same shape as before, but the renderer now produces
|
|
385
|
+
// valid JSON more often (proper iteration, helpers resolved).
|
|
379
386
|
try {
|
|
380
|
-
|
|
381
|
-
if (
|
|
382
|
-
return JSON.parse(
|
|
387
|
+
const trimmed = rendered.trim();
|
|
388
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
389
|
+
return JSON.parse(rendered);
|
|
383
390
|
}
|
|
384
391
|
}
|
|
385
392
|
catch {
|
|
386
|
-
//
|
|
393
|
+
// fall through to string return
|
|
387
394
|
}
|
|
388
|
-
return
|
|
395
|
+
return rendered;
|
|
389
396
|
}
|
|
390
397
|
applyJsonTemplate(rule, templateContext) {
|
|
398
|
+
// Cycle 2 of the template-engine rewrite: JSON templates now go
|
|
399
|
+
// through real Handlebars. Helpers (eq / humanize / pluralize /
|
|
400
|
+
// lowerCase / upperCase) are globally registered at module load.
|
|
401
|
+
// strict:false makes missing variables render as empty strings
|
|
402
|
+
// (old behaviour preserved the literal `{{name}}` — which would
|
|
403
|
+
// break JSON.parse anyway, so this is strictly safer). noEscape
|
|
404
|
+
// because rule output is machine-read JSON, not HTML.
|
|
391
405
|
try {
|
|
392
|
-
|
|
406
|
+
const template = Array.isArray(rule.template.content)
|
|
393
407
|
? rule.template.content.join('\n')
|
|
394
408
|
: rule.template.content;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
return templateContext[key] || match;
|
|
398
|
-
});
|
|
399
|
-
return JSON.parse(template);
|
|
409
|
+
const rendered = Handlebars.compile(template, { strict: false, noEscape: true })(templateContext);
|
|
410
|
+
return JSON.parse(rendered);
|
|
400
411
|
}
|
|
401
412
|
catch (error) {
|
|
402
413
|
throw new Error(`Failed to parse JSON template: ${error instanceof Error ? error.message : String(error)}`);
|
|
403
414
|
}
|
|
404
415
|
}
|
|
405
416
|
applyYamlTemplate(rule, templateContext) {
|
|
417
|
+
// Cycle 3: real Handlebars. Replaces four load-bearing hacks:
|
|
418
|
+
// 1. Hardcoded parentRelationships[0].targetModel substitution
|
|
419
|
+
// — real Handlebars' \`{{#each}}\` iterates every entry.
|
|
420
|
+
// 2. Global /each/ strip — ate nested closers. Real Handlebars
|
|
421
|
+
// pairs opens/closes correctly.
|
|
422
|
+
// 3. Fake \`{{#if required}}\` (always passed through) — real
|
|
423
|
+
// Handlebars evaluates the condition against loop context.
|
|
424
|
+
// 4. Hardcoded \`{{name}}\` → 'sampleAttribute' when parent
|
|
425
|
+
// relationships exist — real Handlebars resolves \`{{name}}\`
|
|
426
|
+
// against the iterated attribute, which is what the template
|
|
427
|
+
// always intended.
|
|
428
|
+
// Plus the processSpeclyHandlebars conditional handler, also gone.
|
|
406
429
|
try {
|
|
407
|
-
|
|
430
|
+
const template = Array.isArray(rule.template.content)
|
|
408
431
|
? rule.template.content.join('\n')
|
|
409
432
|
: rule.template.content;
|
|
410
|
-
// Ensure template is a string
|
|
411
433
|
if (typeof template !== 'string') {
|
|
412
434
|
throw new Error(`Template content must be string or string array, got ${typeof template}`);
|
|
413
435
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const parentRel = templateContext.relationships.parentRelationships[0];
|
|
421
|
-
// Process the each loop for parent relationships
|
|
422
|
-
template = template.replace(/\{\{#each relationships\.parentRelationships\}\}/g, '');
|
|
423
|
-
template = template.replace(/\{\{\/each\}\}/g, '');
|
|
424
|
-
// Replace context variables
|
|
425
|
-
template = template.replace(/\{\{targetModel\}\}/g, parentRel.targetModel || '');
|
|
426
|
-
template = template.replace(/\{\{\.\.\/modelName\}\}/g, templateContext.modelName || '');
|
|
427
|
-
// Handle nested attribute loops
|
|
428
|
-
template = template.replace(/\{\{#each \.\.\/model\.attributes\}\}/g, '');
|
|
429
|
-
// Process the required conditional specifically for attribute loops
|
|
430
|
-
template = template.replace(/\{\{#if required\}\}([^]*?)\{\{\/if\}\}/g, (match, content) => {
|
|
431
|
-
// For attributes, assume required if we're in parent relationship context
|
|
432
|
-
return content;
|
|
433
|
-
});
|
|
434
|
-
template = template.replace(/\{\{name\}\}/g, 'sampleAttribute');
|
|
435
|
-
}
|
|
436
|
-
// Handle many-to-many relationships processing
|
|
437
|
-
if (templateContext.relationships?.manyToManyRelationships?.length > 0) {
|
|
438
|
-
const manyToManyRel = templateContext.relationships.manyToManyRelationships[0];
|
|
439
|
-
// Process the each loop for many-to-many relationships
|
|
440
|
-
template = template.replace(/\{\{#each relationships\.manyToManyRelationships\}\}/g, '');
|
|
441
|
-
template = template.replace(/\{\{\/each\}\}/g, '');
|
|
442
|
-
// Replace context variables
|
|
443
|
-
template = template.replace(/\{\{targetModel\}\}/g, manyToManyRel.targetModel || '');
|
|
444
|
-
template = template.replace(/\{\{\.\.\/modelName\}\}/g, templateContext.modelName || '');
|
|
445
|
-
}
|
|
446
|
-
// Use the shared handlebars processing that handles all conditionals consistently
|
|
447
|
-
template = this.processSpeclyHandlebars(template, templateContext);
|
|
448
|
-
// Clean up any double empty lines that might confuse YAML parser
|
|
449
|
-
template = template.replace(/\n\n+/g, '\n\n');
|
|
450
|
-
// Template processing completed
|
|
451
|
-
// Handlebars processing already done above
|
|
452
|
-
// Parse the processed template as YAML
|
|
453
|
-
const result = yaml.load(template);
|
|
454
|
-
return result;
|
|
436
|
+
const rendered = Handlebars.compile(template, { strict: false, noEscape: true })(templateContext);
|
|
437
|
+
// Collapse runs of blank lines — same cleanup the old path did,
|
|
438
|
+
// still useful because YAML parsers reject trailing garbage in
|
|
439
|
+
// some shapes.
|
|
440
|
+
const cleaned = rendered.replace(/\n\n+/g, '\n\n');
|
|
441
|
+
return yaml.load(cleaned);
|
|
455
442
|
}
|
|
456
443
|
catch (error) {
|
|
457
444
|
throw new Error(`Failed to parse YAML template: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -459,564 +446,55 @@ export class RuleEngine {
|
|
|
459
446
|
}
|
|
460
447
|
applySpeclyTemplate(rule, templateContext) {
|
|
461
448
|
try {
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
//
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
if (rule.pattern === 'LifecycleService') {
|
|
489
|
-
return this.generateLifecycleServiceSpec(templateContext);
|
|
490
|
-
}
|
|
491
|
-
if (rule.pattern === 'RelationshipService') {
|
|
492
|
-
return this.generateRelationshipServiceSpec(templateContext);
|
|
493
|
-
}
|
|
494
|
-
// For other specly templates, process as string
|
|
495
|
-
let template = Array.isArray(rule.template.content)
|
|
449
|
+
// StandardCURED now has a real Handlebars template in
|
|
450
|
+
// entities/src/core/models/inference/controller-rules.json
|
|
451
|
+
// that renders to the equivalent ControllerSpec. Generator
|
|
452
|
+
// method removed. See Cycle 5 in the Handlebars-rewrite plan.
|
|
453
|
+
//
|
|
454
|
+
// ProcessingService / ValidationService / RelationshipService
|
|
455
|
+
// rules + generators fully retired (Cycle 6). See
|
|
456
|
+
// engines/src/inference/DISABLED-RULES.md for the rationale.
|
|
457
|
+
//
|
|
458
|
+
// StandardEvents (Cycle 7) + RelationshipEvents (Cycle 8) now
|
|
459
|
+
// render from real Handlebars templates in
|
|
460
|
+
// entities/src/core/events/inference/event-rules.json.
|
|
461
|
+
// EnhancedListView / AnalyticsView (Cycle 9): retired.
|
|
462
|
+
// Superseded by the view-triggered specialist-views expansion
|
|
463
|
+
// system (specialist_analytics, specialist_dashboard, etc.) —
|
|
464
|
+
// see entities/src/core/views/inference/specialist-views.json
|
|
465
|
+
// and engines/src/inference/DISABLED-RULES.md.
|
|
466
|
+
// IntegrationService (Cycle 10) + LifecycleService (Cycle 11)
|
|
467
|
+
// now render from real Handlebars templates in service-rules.json.
|
|
468
|
+
// Cycle 5: real Handlebars + yaml.load for specly templates that
|
|
469
|
+
// produce a structured object (like the cured_root_controller
|
|
470
|
+
// template which renders a ControllerSpec). Falls back to the
|
|
471
|
+
// raw string if yaml.load fails — for templates that genuinely
|
|
472
|
+
// produce non-YAML output.
|
|
473
|
+
const template = Array.isArray(rule.template.content)
|
|
496
474
|
? rule.template.content.join('\n')
|
|
497
475
|
: rule.template.content;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
template = this.processSpeclyHandlebars(template, templateContext);
|
|
504
|
-
return template;
|
|
505
|
-
}
|
|
506
|
-
catch (error) {
|
|
507
|
-
throw new Error(`Failed to process Specly template: ${error instanceof Error ? error.message : String(error)}`);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
generateCuredControllerSpec(context) {
|
|
511
|
-
const modelName = context.modelName || 'Unknown';
|
|
512
|
-
// Build CURED operations spec
|
|
513
|
-
const curedOps = {
|
|
514
|
-
create: {
|
|
515
|
-
parameters: {
|
|
516
|
-
data: `${modelName} required`
|
|
517
|
-
},
|
|
518
|
-
returns: modelName,
|
|
519
|
-
requires: [
|
|
520
|
-
`${modelName} data is valid`
|
|
521
|
-
],
|
|
522
|
-
ensures: [
|
|
523
|
-
`${modelName} created with unique ID`,
|
|
524
|
-
`${modelName} persisted to storage`
|
|
525
|
-
],
|
|
526
|
-
publishes: [`${modelName}Created`]
|
|
527
|
-
},
|
|
528
|
-
retrieve: {
|
|
529
|
-
parameters: {
|
|
530
|
-
id: 'UUID required'
|
|
531
|
-
},
|
|
532
|
-
returns: modelName,
|
|
533
|
-
requires: [`${modelName} with ID exists`],
|
|
534
|
-
ensures: [`Returns complete ${modelName} details`]
|
|
535
|
-
},
|
|
536
|
-
retrieve_many: {
|
|
537
|
-
parameters: {
|
|
538
|
-
filters: `${modelName}Filter optional`,
|
|
539
|
-
limit: 'Integer default=20',
|
|
540
|
-
offset: 'Integer default=0'
|
|
541
|
-
},
|
|
542
|
-
returns: `Array[${modelName}]`,
|
|
543
|
-
ensures: [
|
|
544
|
-
`Returns paginated list of ${modelName}s`,
|
|
545
|
-
`Applies filters if specified`
|
|
546
|
-
]
|
|
547
|
-
},
|
|
548
|
-
update: {
|
|
549
|
-
parameters: {
|
|
550
|
-
id: 'UUID required',
|
|
551
|
-
updates: `Object required`
|
|
552
|
-
},
|
|
553
|
-
returns: modelName,
|
|
554
|
-
requires: [
|
|
555
|
-
`${modelName} with ID exists`,
|
|
556
|
-
`Update data is valid`
|
|
557
|
-
],
|
|
558
|
-
ensures: [
|
|
559
|
-
`${modelName} attributes updated`,
|
|
560
|
-
`Version number incremented`
|
|
561
|
-
],
|
|
562
|
-
publishes: [`${modelName}Updated`]
|
|
563
|
-
},
|
|
564
|
-
delete: {
|
|
565
|
-
parameters: {
|
|
566
|
-
id: 'UUID required',
|
|
567
|
-
soft: 'Boolean default=true'
|
|
568
|
-
},
|
|
569
|
-
returns: 'Boolean',
|
|
570
|
-
requires: [`${modelName} with ID exists`],
|
|
571
|
-
ensures: [`${modelName} marked as deleted`],
|
|
572
|
-
publishes: [`${modelName}Deleted`]
|
|
573
|
-
},
|
|
574
|
-
validate: {
|
|
575
|
-
parameters: {
|
|
576
|
-
data: `${modelName} required`,
|
|
577
|
-
operation: 'String required'
|
|
578
|
-
},
|
|
579
|
-
returns: 'ValidationResult',
|
|
580
|
-
requires: [
|
|
581
|
-
`Operation is one of: create, update, evolve`,
|
|
582
|
-
`Data structure matches ${modelName} schema`
|
|
583
|
-
],
|
|
584
|
-
ensures: [
|
|
585
|
-
`Returns validation status and errors`,
|
|
586
|
-
`No side effects - dry run only`
|
|
587
|
-
]
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
// Add dynamic required field validation
|
|
591
|
-
if (context.model?.attributes) {
|
|
592
|
-
const requiredFields = context.model.attributes.filter((attr) => attr.required);
|
|
593
|
-
if (requiredFields.length > 0) {
|
|
594
|
-
requiredFields.forEach((attr) => {
|
|
595
|
-
curedOps.create.requires.push(`${attr.name} is not empty`);
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
// Add lifecycle operations if model has lifecycle
|
|
600
|
-
if (context.model?.lifecycle) {
|
|
601
|
-
curedOps.evolve = {
|
|
602
|
-
parameters: {
|
|
603
|
-
id: 'UUID required',
|
|
604
|
-
from: `${modelName}State required`,
|
|
605
|
-
to: `${modelName}State required`,
|
|
606
|
-
reason: 'String optional'
|
|
607
|
-
},
|
|
608
|
-
returns: modelName,
|
|
609
|
-
requires: [
|
|
610
|
-
`${modelName} with ID exists`,
|
|
611
|
-
`${modelName} current state is 'from' state`,
|
|
612
|
-
`Transition from 'from' to 'to' is valid`
|
|
613
|
-
],
|
|
614
|
-
ensures: [
|
|
615
|
-
`${modelName} state changed to 'to' state`,
|
|
616
|
-
`State transition logged`
|
|
617
|
-
],
|
|
618
|
-
publishes: [`${modelName}Evolved`]
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
return {
|
|
622
|
-
description: `Auto-generated CURED controller for ${modelName}`,
|
|
623
|
-
model: modelName,
|
|
624
|
-
cured: curedOps
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
generateProcessingServiceSpec(context) {
|
|
628
|
-
const modelName = context.modelName || 'Unknown';
|
|
629
|
-
const operations = {
|
|
630
|
-
[`handle${modelName}Creation`]: {
|
|
631
|
-
parameters: {
|
|
632
|
-
event: `${modelName}CreatedEvent required`
|
|
633
|
-
},
|
|
634
|
-
returns: 'Void',
|
|
635
|
-
requires: ['Event data is valid'],
|
|
636
|
-
ensures: [
|
|
637
|
-
`${modelName} creation processing completed`,
|
|
638
|
-
'Related systems notified'
|
|
639
|
-
]
|
|
640
|
-
},
|
|
641
|
-
[`handle${modelName}Update`]: {
|
|
642
|
-
parameters: {
|
|
643
|
-
event: `${modelName}UpdatedEvent required`
|
|
644
|
-
},
|
|
645
|
-
returns: 'Void',
|
|
646
|
-
requires: ['Event data is valid'],
|
|
647
|
-
ensures: [
|
|
648
|
-
`${modelName} update processing completed`,
|
|
649
|
-
'Change propagated to dependent systems'
|
|
650
|
-
]
|
|
651
|
-
},
|
|
652
|
-
[`validate${modelName}`]: {
|
|
653
|
-
parameters: {
|
|
654
|
-
data: `${modelName} required`,
|
|
655
|
-
validationContext: 'Object optional'
|
|
656
|
-
},
|
|
657
|
-
returns: 'ValidationResult',
|
|
658
|
-
requires: [`${modelName} data is provided`],
|
|
659
|
-
ensures: [
|
|
660
|
-
'Complete validation performed',
|
|
661
|
-
'Business rules checked'
|
|
662
|
-
]
|
|
663
|
-
},
|
|
664
|
-
[`process${modelName}BusinessRules`]: {
|
|
665
|
-
parameters: {
|
|
666
|
-
id: 'UUID required',
|
|
667
|
-
ruleContext: 'Object required'
|
|
668
|
-
},
|
|
669
|
-
returns: 'BusinessRuleResult',
|
|
670
|
-
requires: [
|
|
671
|
-
`${modelName} exists`,
|
|
672
|
-
'Rule context is valid'
|
|
673
|
-
],
|
|
674
|
-
ensures: [
|
|
675
|
-
'All applicable business rules processed',
|
|
676
|
-
'Results documented'
|
|
677
|
-
]
|
|
678
|
-
}
|
|
679
|
-
};
|
|
680
|
-
const subscribes_to = {
|
|
681
|
-
[`${modelName}Created`]: `handle${modelName}Creation`,
|
|
682
|
-
[`${modelName}Updated`]: `handle${modelName}Update`
|
|
683
|
-
};
|
|
684
|
-
// Add lifecycle operations if model has lifecycle
|
|
685
|
-
if (context.model?.lifecycle) {
|
|
686
|
-
operations[`handle${modelName}Evolution`] = {
|
|
687
|
-
parameters: {
|
|
688
|
-
event: `${modelName}EvolvedEvent required`
|
|
689
|
-
},
|
|
690
|
-
returns: 'Void',
|
|
691
|
-
requires: ['Lifecycle transition is valid'],
|
|
692
|
-
ensures: [
|
|
693
|
-
'Lifecycle change processed',
|
|
694
|
-
'State-dependent actions triggered'
|
|
695
|
-
]
|
|
696
|
-
};
|
|
697
|
-
subscribes_to[`${modelName}Evolved`] = `handle${modelName}Evolution`;
|
|
698
|
-
}
|
|
699
|
-
return {
|
|
700
|
-
description: `Handles complex business logic and processing for ${modelName}`,
|
|
701
|
-
subscribes_to,
|
|
702
|
-
operations
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
generateValidationServiceSpec(context) {
|
|
706
|
-
const modelName = context.modelName || 'Unknown';
|
|
707
|
-
const operations = {
|
|
708
|
-
validateCreate: {
|
|
709
|
-
parameters: {
|
|
710
|
-
data: `${modelName} required`,
|
|
711
|
-
parentContext: 'Object optional'
|
|
712
|
-
},
|
|
713
|
-
returns: 'ValidationResult',
|
|
714
|
-
requires: [`${modelName} data is provided`],
|
|
715
|
-
ensures: [
|
|
716
|
-
'All create validations performed',
|
|
717
|
-
'Parent relationships validated',
|
|
718
|
-
'Business constraints checked'
|
|
719
|
-
]
|
|
720
|
-
},
|
|
721
|
-
validateUpdate: {
|
|
722
|
-
parameters: {
|
|
723
|
-
id: 'UUID required',
|
|
724
|
-
updates: 'Object required',
|
|
725
|
-
currentState: `${modelName} required`
|
|
726
|
-
},
|
|
727
|
-
returns: 'ValidationResult',
|
|
728
|
-
requires: [
|
|
729
|
-
`${modelName} exists`,
|
|
730
|
-
'Current state is provided'
|
|
731
|
-
],
|
|
732
|
-
ensures: [
|
|
733
|
-
'All update validations performed',
|
|
734
|
-
'State transition rules checked'
|
|
735
|
-
]
|
|
736
|
-
},
|
|
737
|
-
validateBusinessRules: {
|
|
738
|
-
parameters: {
|
|
739
|
-
data: `${modelName} required`,
|
|
740
|
-
ruleSet: 'String optional'
|
|
741
|
-
},
|
|
742
|
-
returns: 'BusinessRuleValidationResult',
|
|
743
|
-
ensures: [
|
|
744
|
-
'All business rules validated',
|
|
745
|
-
'Cross-model dependencies checked'
|
|
746
|
-
]
|
|
747
|
-
}
|
|
748
|
-
};
|
|
749
|
-
// Add lifecycle validation if model has lifecycle
|
|
750
|
-
if (context.model?.lifecycle) {
|
|
751
|
-
operations.validateStateTransition = {
|
|
752
|
-
parameters: {
|
|
753
|
-
id: 'UUID required',
|
|
754
|
-
fromState: 'String required',
|
|
755
|
-
toState: 'String required',
|
|
756
|
-
transitionContext: 'Object optional'
|
|
757
|
-
},
|
|
758
|
-
returns: 'ValidationResult',
|
|
759
|
-
requires: [
|
|
760
|
-
`${modelName} exists`,
|
|
761
|
-
'States are valid'
|
|
762
|
-
],
|
|
763
|
-
ensures: [
|
|
764
|
-
'Transition rules validated',
|
|
765
|
-
'Prerequisites checked'
|
|
766
|
-
]
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
return {
|
|
770
|
-
description: `Provides comprehensive validation for ${modelName} data`,
|
|
771
|
-
operations
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
generateStandardEventsSpec(context) {
|
|
775
|
-
const modelName = context.modelName || 'Unknown';
|
|
776
|
-
const events = {};
|
|
777
|
-
// Created event
|
|
778
|
-
events[`${modelName}Created`] = {
|
|
779
|
-
description: `${modelName} was created`,
|
|
780
|
-
attributes: {
|
|
781
|
-
id: 'UUID required',
|
|
782
|
-
timestamp: 'DateTime required',
|
|
783
|
-
createdBy: 'UUID required',
|
|
784
|
-
version: 'Integer default=1'
|
|
785
|
-
}
|
|
786
|
-
};
|
|
787
|
-
// Updated event
|
|
788
|
-
events[`${modelName}Updated`] = {
|
|
789
|
-
description: `${modelName} was updated`,
|
|
790
|
-
attributes: {
|
|
791
|
-
id: 'UUID required',
|
|
792
|
-
changedFields: 'Array required',
|
|
793
|
-
previousValues: 'Object optional',
|
|
794
|
-
newValues: 'Object required',
|
|
795
|
-
timestamp: 'DateTime required',
|
|
796
|
-
updatedBy: 'UUID required',
|
|
797
|
-
version: 'Integer required'
|
|
798
|
-
}
|
|
799
|
-
};
|
|
800
|
-
// Deleted event
|
|
801
|
-
events[`${modelName}Deleted`] = {
|
|
802
|
-
description: `${modelName} was deleted`,
|
|
803
|
-
attributes: {
|
|
804
|
-
id: 'UUID required',
|
|
805
|
-
deletedBy: 'UUID required',
|
|
806
|
-
timestamp: 'DateTime required',
|
|
807
|
-
soft: 'Boolean default=true',
|
|
808
|
-
reason: 'String optional'
|
|
809
|
-
}
|
|
810
|
-
};
|
|
811
|
-
// Add key attributes from model to events
|
|
812
|
-
if (context.model?.attributes) {
|
|
813
|
-
const keyAttributes = context.model.attributes.filter((attr) => attr.name === 'name' || attr.name === 'title' || attr.name === 'status');
|
|
814
|
-
keyAttributes.forEach((attr) => {
|
|
815
|
-
events[`${modelName}Created`].attributes[attr.name] = `${attr.type} required`;
|
|
816
|
-
events[`${modelName}Updated`].attributes[attr.name] = `${attr.type} optional`;
|
|
817
|
-
if (attr.name === 'name') {
|
|
818
|
-
events[`${modelName}Deleted`].attributes[attr.name] = `${attr.type} optional`;
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
return events;
|
|
823
|
-
}
|
|
824
|
-
generateViewSpec(pattern, context) {
|
|
825
|
-
const modelName = context.modelName || 'Unknown';
|
|
826
|
-
if (pattern === 'EnhancedListView') {
|
|
827
|
-
return {
|
|
828
|
-
[`${modelName}EnhancedListView`]: {
|
|
829
|
-
type: 'list',
|
|
830
|
-
model: modelName,
|
|
831
|
-
description: `Enhanced list view for ${modelName} with advanced filtering`,
|
|
832
|
-
subscribes_to: [
|
|
833
|
-
`${modelName}Created`,
|
|
834
|
-
`${modelName}Updated`,
|
|
835
|
-
`${modelName}Deleted`
|
|
836
|
-
],
|
|
837
|
-
uiComponents: {
|
|
838
|
-
advancedSearch: {
|
|
839
|
-
type: 'AdvancedSearchInput',
|
|
840
|
-
properties: {
|
|
841
|
-
placeholder: `Search ${modelName}s...`,
|
|
842
|
-
debounce: 300,
|
|
843
|
-
enableSuggestions: true
|
|
844
|
-
}
|
|
845
|
-
},
|
|
846
|
-
advancedFilters: {
|
|
847
|
-
type: 'FilterPanel',
|
|
848
|
-
properties: {
|
|
849
|
-
model: modelName,
|
|
850
|
-
collapsible: true
|
|
851
|
-
}
|
|
852
|
-
},
|
|
853
|
-
[`${modelName.toLowerCase()}Table`]: {
|
|
854
|
-
type: 'DataTable',
|
|
855
|
-
properties: {
|
|
856
|
-
model: modelName,
|
|
857
|
-
pagination: {
|
|
858
|
-
enabled: true,
|
|
859
|
-
pageSize: 25,
|
|
860
|
-
showSizeSelector: true
|
|
861
|
-
},
|
|
862
|
-
selection: {
|
|
863
|
-
enabled: true,
|
|
864
|
-
multiSelect: true
|
|
865
|
-
},
|
|
866
|
-
sorting: {
|
|
867
|
-
enabled: true,
|
|
868
|
-
multiColumn: true
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
else if (pattern === 'AnalyticsView') {
|
|
877
|
-
return {
|
|
878
|
-
[`${modelName}AnalyticsView`]: {
|
|
879
|
-
type: 'dashboard',
|
|
880
|
-
model: modelName,
|
|
881
|
-
description: `Analytics and reporting dashboard for ${modelName}`,
|
|
882
|
-
subscribes_to: [
|
|
883
|
-
`${modelName}Created`,
|
|
884
|
-
`${modelName}Updated`,
|
|
885
|
-
`${modelName}Deleted`
|
|
886
|
-
],
|
|
887
|
-
uiComponents: {
|
|
888
|
-
keyMetrics: {
|
|
889
|
-
type: 'MetricsSummary',
|
|
890
|
-
properties: {
|
|
891
|
-
title: `${modelName} Key Metrics`,
|
|
892
|
-
refreshInterval: 300000
|
|
893
|
-
}
|
|
894
|
-
},
|
|
895
|
-
trendsChart: {
|
|
896
|
-
type: 'TimeSeriesChart',
|
|
897
|
-
properties: {
|
|
898
|
-
title: `${modelName} Trends Over Time`,
|
|
899
|
-
chartType: 'line',
|
|
900
|
-
dateRange: 'last30Days'
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
}
|
|
476
|
+
const rendered = Handlebars.compile(template, { strict: false, noEscape: true })(templateContext);
|
|
477
|
+
try {
|
|
478
|
+
const trimmed = rendered.trim();
|
|
479
|
+
if (trimmed.includes(':') || trimmed.startsWith('[') || trimmed.startsWith('{')) {
|
|
480
|
+
return yaml.load(rendered);
|
|
904
481
|
}
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
return {};
|
|
908
|
-
}
|
|
909
|
-
processSpeclyHandlebars(template, context) {
|
|
910
|
-
// Process {{#if model.lifecycle}} blocks
|
|
911
|
-
template = template.replace(/\{\{#if model\.lifecycle\}\}([^]*?)\{\{\/if\}\}/g, (match, content) => {
|
|
912
|
-
return context.model?.lifecycle ? content : '';
|
|
913
|
-
});
|
|
914
|
-
// Process {{#each model.attributes}} blocks
|
|
915
|
-
template = template.replace(/\{\{#each model\.attributes\}\}([^]*?)\{\{\/each\}\}/g, (match, content) => {
|
|
916
|
-
if (!context.model?.attributes)
|
|
917
|
-
return '';
|
|
918
|
-
if (typeof content !== 'string') {
|
|
919
|
-
throw new Error(`content in model.attributes each loop is not a string: ${typeof content}`);
|
|
920
482
|
}
|
|
921
|
-
|
|
922
|
-
return
|
|
923
|
-
.replace(/\{\{type\}\}/g, attr.type)
|
|
924
|
-
.replace(/\{\{required\}\}/g, attr.required ? 'true' : 'false');
|
|
925
|
-
}).join('');
|
|
926
|
-
});
|
|
927
|
-
// Process {{#if required}} blocks within attribute loops
|
|
928
|
-
template = template.replace(/\{\{#if required\}\}([^]*?)\{\{\/if\}\}/g, (match, content) => {
|
|
929
|
-
// This will be processed within the context of each attribute
|
|
930
|
-
return content; // Keep the content, the required check happens in the attribute loop
|
|
931
|
-
});
|
|
932
|
-
// Process {{#each relationships.cascadeDeleteTargets}} blocks
|
|
933
|
-
template = template.replace(/\{\{#each relationships\.cascadeDeleteTargets\}\}([^]*?)\{\{\/each\}\}/g, (match, content) => {
|
|
934
|
-
if (!context.relationships?.cascadeDeleteTargets)
|
|
935
|
-
return '';
|
|
936
|
-
return context.relationships.cascadeDeleteTargets.map((target) => {
|
|
937
|
-
return content.replace(/\{\{this\}\}/g, target);
|
|
938
|
-
}).join('');
|
|
939
|
-
});
|
|
940
|
-
// Process {{#each relationships.parentRelationships}} blocks
|
|
941
|
-
template = template.replace(/\{\{#each relationships\.parentRelationships\}\}([^]*?)\{\{\/each\}\}/g, (match, content) => {
|
|
942
|
-
if (!context.relationships?.parentRelationships)
|
|
943
|
-
return '';
|
|
944
|
-
return context.relationships.parentRelationships.map((rel) => {
|
|
945
|
-
let processedContent = content;
|
|
946
|
-
// Ensure content is a string
|
|
947
|
-
if (typeof processedContent !== 'string') {
|
|
948
|
-
throw new Error(`processedContent is not a string: ${typeof processedContent}`);
|
|
949
|
-
}
|
|
950
|
-
// Replace relationship-specific variables
|
|
951
|
-
processedContent = processedContent.replace(/\{\{targetModel\}\}/g, rel.targetModel)
|
|
952
|
-
.replace(/\{\{\.\.\/modelName\}\}/g, context.modelName || '')
|
|
953
|
-
.replace(/\{\{\.\.\/\.\.\/modelName\}\}/g, context.modelName || '');
|
|
954
|
-
// Process nested {{#each ../model.attributes}} blocks within this content
|
|
955
|
-
processedContent = processedContent.replace(/\{\{#each \.\.\/model\.attributes\}\}([^]*?)\{\{\/each\}\}/g, (nestedMatch, nestedContent) => {
|
|
956
|
-
if (!context.model?.attributes)
|
|
957
|
-
return '';
|
|
958
|
-
if (typeof nestedContent !== 'string') {
|
|
959
|
-
throw new Error(`nestedContent is not a string: ${typeof nestedContent}`);
|
|
960
|
-
}
|
|
961
|
-
return context.model.attributes.map((attr) => {
|
|
962
|
-
if (attr.required) {
|
|
963
|
-
return nestedContent.replace(/\{\{name\}\}/g, attr.name)
|
|
964
|
-
.replace(/\{\{type\}\}/g, attr.type);
|
|
965
|
-
}
|
|
966
|
-
return '';
|
|
967
|
-
}).join('');
|
|
968
|
-
});
|
|
969
|
-
// Process nested {{#if required}} blocks
|
|
970
|
-
processedContent = processedContent.replace(/\{\{#if required\}\}([^]*?)\{\{\/if\}\}/g, (ifMatch, ifContent) => {
|
|
971
|
-
return ifContent; // Keep content, filtering happens in the attributes loop
|
|
972
|
-
});
|
|
973
|
-
return processedContent;
|
|
974
|
-
}).join('');
|
|
975
|
-
});
|
|
976
|
-
return template;
|
|
977
|
-
}
|
|
978
|
-
processHandlebarsInJson(obj, context) {
|
|
979
|
-
if (typeof obj === 'string') {
|
|
980
|
-
// Process Handlebars variables
|
|
981
|
-
let result = obj.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
982
|
-
return context[key] || match;
|
|
983
|
-
});
|
|
984
|
-
// Process Handlebars conditionals and loops (basic implementation)
|
|
985
|
-
result = this.processHandlebarsConditionals(result, context);
|
|
986
|
-
return result;
|
|
987
|
-
}
|
|
988
|
-
else if (Array.isArray(obj)) {
|
|
989
|
-
return obj.map(item => this.processHandlebarsInJson(item, context));
|
|
990
|
-
}
|
|
991
|
-
else if (obj && typeof obj === 'object') {
|
|
992
|
-
const processed = {};
|
|
993
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
994
|
-
const processedKey = this.processHandlebarsInJson(key, context);
|
|
995
|
-
processed[processedKey] = this.processHandlebarsInJson(value, context);
|
|
483
|
+
catch {
|
|
484
|
+
// YAML parse failed — return the raw rendered string.
|
|
996
485
|
}
|
|
997
|
-
return
|
|
486
|
+
return rendered;
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
throw new Error(`Failed to process Specly template: ${error instanceof Error ? error.message : String(error)}`);
|
|
998
490
|
}
|
|
999
|
-
return obj;
|
|
1000
|
-
}
|
|
1001
|
-
processHandlebarsConditionals(template, context) {
|
|
1002
|
-
// Basic conditional processing for {{#if}} and {{#each}}
|
|
1003
|
-
// This is a simplified version - a full implementation would use the Handlebars library
|
|
1004
|
-
// Process {{#if model.lifecycle}} blocks
|
|
1005
|
-
template = template.replace(/\{\{#if model\.lifecycle\}\}([^]*?)\{\{\/if\}\}/g, (match, content) => {
|
|
1006
|
-
return context.model?.lifecycle ? content : '';
|
|
1007
|
-
});
|
|
1008
|
-
// Process {{#each model.attributes}} blocks
|
|
1009
|
-
template = template.replace(/\{\{#each model\.attributes\}\}([^]*?)\{\{\/each\}\}/g, (match, content) => {
|
|
1010
|
-
if (!context.model?.attributes)
|
|
1011
|
-
return '';
|
|
1012
|
-
return context.model.attributes.map((attr) => {
|
|
1013
|
-
return content.replace(/\{\{name\}\}/g, attr.name)
|
|
1014
|
-
.replace(/\{\{type\}\}/g, attr.type)
|
|
1015
|
-
.replace(/\{\{required\}\}/g, attr.required);
|
|
1016
|
-
}).join('');
|
|
1017
|
-
});
|
|
1018
|
-
return template;
|
|
1019
491
|
}
|
|
492
|
+
// processSpeclyHandlebars / processHandlebarsInJson /
|
|
493
|
+
// processHandlebarsConditionals — removed in Cycle 3. They were
|
|
494
|
+
// the home-rolled string-substitution layer that the four
|
|
495
|
+
// apply*Template methods delegated to. All four methods now go
|
|
496
|
+
// through real Handlebars (Handlebars.compile + render), so these
|
|
497
|
+
// have zero callers and are gone.
|
|
1020
498
|
convertJsonToSpecly(obj) {
|
|
1021
499
|
// Convert processed JSON object to .specly syntax
|
|
1022
500
|
let result = '';
|
|
@@ -1062,352 +540,5 @@ export class RuleEngine {
|
|
|
1062
540
|
}
|
|
1063
541
|
return result;
|
|
1064
542
|
}
|
|
1065
|
-
generateIntegrationServiceSpec(context) {
|
|
1066
|
-
const modelName = context.modelName || 'Unknown';
|
|
1067
|
-
const operations = {
|
|
1068
|
-
syncToExternalSystems: {
|
|
1069
|
-
parameters: {
|
|
1070
|
-
event: `${modelName}CreatedEvent required`
|
|
1071
|
-
},
|
|
1072
|
-
returns: 'IntegrationResult',
|
|
1073
|
-
requires: ['External systems are available'],
|
|
1074
|
-
ensures: [
|
|
1075
|
-
`${modelName} synchronized to all external systems`,
|
|
1076
|
-
'Integration status tracked'
|
|
1077
|
-
]
|
|
1078
|
-
},
|
|
1079
|
-
updateExternalSystems: {
|
|
1080
|
-
parameters: {
|
|
1081
|
-
event: `${modelName}UpdatedEvent required`
|
|
1082
|
-
},
|
|
1083
|
-
returns: 'IntegrationResult',
|
|
1084
|
-
requires: ['External systems are available'],
|
|
1085
|
-
ensures: [
|
|
1086
|
-
'Changes synchronized to external systems',
|
|
1087
|
-
'Sync conflicts resolved'
|
|
1088
|
-
]
|
|
1089
|
-
},
|
|
1090
|
-
removeFromExternalSystems: {
|
|
1091
|
-
parameters: {
|
|
1092
|
-
event: `${modelName}DeletedEvent required`
|
|
1093
|
-
},
|
|
1094
|
-
returns: 'IntegrationResult',
|
|
1095
|
-
ensures: [
|
|
1096
|
-
`${modelName} removed from external systems`,
|
|
1097
|
-
'Cleanup operations completed'
|
|
1098
|
-
]
|
|
1099
|
-
},
|
|
1100
|
-
pullFromExternalSystem: {
|
|
1101
|
-
parameters: {
|
|
1102
|
-
externalSystemId: 'String required',
|
|
1103
|
-
lastSyncTime: 'DateTime optional'
|
|
1104
|
-
},
|
|
1105
|
-
returns: `Array[${modelName}]`,
|
|
1106
|
-
requires: ['External system is accessible'],
|
|
1107
|
-
ensures: [
|
|
1108
|
-
'Latest data retrieved from external system',
|
|
1109
|
-
'Sync timestamp updated'
|
|
1110
|
-
]
|
|
1111
|
-
},
|
|
1112
|
-
pushToExternalSystem: {
|
|
1113
|
-
parameters: {
|
|
1114
|
-
id: 'UUID required',
|
|
1115
|
-
externalSystemId: 'String required',
|
|
1116
|
-
forceSync: 'Boolean default=false'
|
|
1117
|
-
},
|
|
1118
|
-
returns: 'IntegrationResult',
|
|
1119
|
-
requires: [
|
|
1120
|
-
`${modelName} exists`,
|
|
1121
|
-
'External system is accessible'
|
|
1122
|
-
],
|
|
1123
|
-
ensures: [
|
|
1124
|
-
`${modelName} pushed to external system`,
|
|
1125
|
-
'Integration status updated'
|
|
1126
|
-
]
|
|
1127
|
-
}
|
|
1128
|
-
};
|
|
1129
|
-
return {
|
|
1130
|
-
description: `Manages external system integration for ${modelName}`,
|
|
1131
|
-
subscribes_to: {
|
|
1132
|
-
[`${modelName}Created`]: 'syncToExternalSystems',
|
|
1133
|
-
[`${modelName}Updated`]: 'updateExternalSystems',
|
|
1134
|
-
[`${modelName}Deleted`]: 'removeFromExternalSystems'
|
|
1135
|
-
},
|
|
1136
|
-
operations
|
|
1137
|
-
};
|
|
1138
|
-
}
|
|
1139
|
-
generateLifecycleServiceSpec(context) {
|
|
1140
|
-
const modelName = context.modelName || 'Unknown';
|
|
1141
|
-
const operations = {
|
|
1142
|
-
handleStateChange: {
|
|
1143
|
-
parameters: {
|
|
1144
|
-
event: `${modelName}EvolvedEvent required`
|
|
1145
|
-
},
|
|
1146
|
-
returns: 'Void',
|
|
1147
|
-
requires: ['State transition is valid'],
|
|
1148
|
-
ensures: [
|
|
1149
|
-
'State-dependent actions triggered',
|
|
1150
|
-
'Transition side-effects processed'
|
|
1151
|
-
]
|
|
1152
|
-
},
|
|
1153
|
-
validateTransition: {
|
|
1154
|
-
parameters: {
|
|
1155
|
-
id: 'UUID required',
|
|
1156
|
-
fromState: 'String required',
|
|
1157
|
-
toState: 'String required',
|
|
1158
|
-
context: 'Object optional'
|
|
1159
|
-
},
|
|
1160
|
-
returns: 'TransitionValidationResult',
|
|
1161
|
-
requires: [
|
|
1162
|
-
`${modelName} exists`,
|
|
1163
|
-
'States are valid lifecycle states'
|
|
1164
|
-
],
|
|
1165
|
-
ensures: [
|
|
1166
|
-
'Transition prerequisites validated',
|
|
1167
|
-
'Business rules checked'
|
|
1168
|
-
]
|
|
1169
|
-
},
|
|
1170
|
-
getAvailableTransitions: {
|
|
1171
|
-
parameters: {
|
|
1172
|
-
[`${modelName}Id`]: 'UUID required'
|
|
1173
|
-
},
|
|
1174
|
-
returns: 'Array[TransitionOption]',
|
|
1175
|
-
requires: [`${modelName} exists`],
|
|
1176
|
-
ensures: ['Returns all valid transitions from current state']
|
|
1177
|
-
},
|
|
1178
|
-
getStateHistory: {
|
|
1179
|
-
parameters: {
|
|
1180
|
-
id: 'UUID required',
|
|
1181
|
-
limit: 'Integer default=50'
|
|
1182
|
-
},
|
|
1183
|
-
returns: 'Array[StateHistoryEntry]',
|
|
1184
|
-
requires: [`${modelName} exists`],
|
|
1185
|
-
ensures: ['Returns chronological state change history']
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1188
|
-
// Add dynamic transition operations if lifecycle exists
|
|
1189
|
-
if (context.model?.lifecycle?.transitions) {
|
|
1190
|
-
context.model.lifecycle.transitions.forEach((transition) => {
|
|
1191
|
-
const operationName = `execute${transition.name}Transition`;
|
|
1192
|
-
operations[operationName] = {
|
|
1193
|
-
parameters: {
|
|
1194
|
-
id: 'UUID required',
|
|
1195
|
-
reason: 'String optional',
|
|
1196
|
-
context: 'Object optional'
|
|
1197
|
-
},
|
|
1198
|
-
returns: modelName,
|
|
1199
|
-
requires: [
|
|
1200
|
-
`${modelName} exists`,
|
|
1201
|
-
`${modelName} current state is '${transition.from}'`
|
|
1202
|
-
],
|
|
1203
|
-
ensures: [
|
|
1204
|
-
`${modelName} state changed to '${transition.to}'`,
|
|
1205
|
-
'Transition side-effects completed'
|
|
1206
|
-
]
|
|
1207
|
-
};
|
|
1208
|
-
});
|
|
1209
|
-
}
|
|
1210
|
-
return {
|
|
1211
|
-
description: `Manages lifecycle transitions and state-dependent behavior for ${modelName}`,
|
|
1212
|
-
subscribes_to: {
|
|
1213
|
-
[`${modelName}Evolved`]: 'handleStateChange'
|
|
1214
|
-
},
|
|
1215
|
-
operations
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
generateRelationshipServiceSpec(context) {
|
|
1219
|
-
const modelName = context.modelName || 'Unknown';
|
|
1220
|
-
const operations = {
|
|
1221
|
-
handleChildAdded: {
|
|
1222
|
-
parameters: {
|
|
1223
|
-
event: 'ChildEntityEvent required'
|
|
1224
|
-
},
|
|
1225
|
-
returns: 'Void',
|
|
1226
|
-
ensures: [
|
|
1227
|
-
'Parent relationship established',
|
|
1228
|
-
'Relationship integrity maintained'
|
|
1229
|
-
]
|
|
1230
|
-
},
|
|
1231
|
-
handleChildRemoved: {
|
|
1232
|
-
parameters: {
|
|
1233
|
-
event: 'ChildEntityEvent required'
|
|
1234
|
-
},
|
|
1235
|
-
returns: 'Void',
|
|
1236
|
-
ensures: [
|
|
1237
|
-
'Parent relationship cleaned up',
|
|
1238
|
-
'Cascade rules applied'
|
|
1239
|
-
]
|
|
1240
|
-
},
|
|
1241
|
-
validateRelationshipIntegrity: {
|
|
1242
|
-
parameters: {
|
|
1243
|
-
id: 'UUID required',
|
|
1244
|
-
relationshipType: 'String optional'
|
|
1245
|
-
},
|
|
1246
|
-
returns: 'IntegrityValidationResult',
|
|
1247
|
-
requires: [`${modelName} exists`],
|
|
1248
|
-
ensures: [
|
|
1249
|
-
'All relationships validated for integrity',
|
|
1250
|
-
'Constraint violations identified'
|
|
1251
|
-
]
|
|
1252
|
-
},
|
|
1253
|
-
repairRelationshipIntegrity: {
|
|
1254
|
-
parameters: {
|
|
1255
|
-
id: 'UUID required',
|
|
1256
|
-
repairOptions: 'RepairOptions required'
|
|
1257
|
-
},
|
|
1258
|
-
returns: 'RepairResult',
|
|
1259
|
-
requires: [
|
|
1260
|
-
`${modelName} exists`,
|
|
1261
|
-
'Repair options are valid'
|
|
1262
|
-
],
|
|
1263
|
-
ensures: [
|
|
1264
|
-
'Relationship integrity issues resolved',
|
|
1265
|
-
'Repair actions logged'
|
|
1266
|
-
]
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
1269
|
-
const subscribes_to = {};
|
|
1270
|
-
// Add child relationship subscriptions
|
|
1271
|
-
if (context.relationships?.childRelationships) {
|
|
1272
|
-
context.relationships.childRelationships.forEach((rel) => {
|
|
1273
|
-
subscribes_to[`${rel.targetModel}Created`] = 'handleChildAdded';
|
|
1274
|
-
subscribes_to[`${rel.targetModel}Deleted`] = 'handleChildRemoved';
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
// Add many-to-many relationship subscriptions and operations
|
|
1278
|
-
if (context.relationships?.manyToManyRelationships) {
|
|
1279
|
-
context.relationships.manyToManyRelationships.forEach((rel) => {
|
|
1280
|
-
subscribes_to[`${modelName}AssociatedWith${rel.targetModel}`] = 'handleAssociationAdded';
|
|
1281
|
-
subscribes_to[`${modelName}DisassociatedFrom${rel.targetModel}`] = 'handleAssociationRemoved';
|
|
1282
|
-
operations.handleAssociationAdded = {
|
|
1283
|
-
parameters: {
|
|
1284
|
-
event: 'AssociationEvent required'
|
|
1285
|
-
},
|
|
1286
|
-
returns: 'Void',
|
|
1287
|
-
ensures: [
|
|
1288
|
-
'Association established',
|
|
1289
|
-
'Association metadata updated'
|
|
1290
|
-
]
|
|
1291
|
-
};
|
|
1292
|
-
operations.handleAssociationRemoved = {
|
|
1293
|
-
parameters: {
|
|
1294
|
-
event: 'DisassociationEvent required'
|
|
1295
|
-
},
|
|
1296
|
-
returns: 'Void',
|
|
1297
|
-
ensures: [
|
|
1298
|
-
'Association removed',
|
|
1299
|
-
'Related data cleaned up'
|
|
1300
|
-
]
|
|
1301
|
-
};
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
return {
|
|
1305
|
-
description: `Manages complex relationships and ensures relationship integrity for ${modelName}`,
|
|
1306
|
-
subscribes_to,
|
|
1307
|
-
operations
|
|
1308
|
-
};
|
|
1309
|
-
}
|
|
1310
|
-
generateRelationshipEventsSpec(context) {
|
|
1311
|
-
const modelName = context.modelName || 'Unknown';
|
|
1312
|
-
const events = {};
|
|
1313
|
-
// Parent relationship events
|
|
1314
|
-
if (context.relationships?.parentRelationships) {
|
|
1315
|
-
context.relationships.parentRelationships.forEach((rel) => {
|
|
1316
|
-
const targetModel = rel.targetModel;
|
|
1317
|
-
// Child created within parent
|
|
1318
|
-
events[`${modelName}CreatedIn${targetModel}`] = {
|
|
1319
|
-
description: `${modelName} was created within ${targetModel}`,
|
|
1320
|
-
attributes: {
|
|
1321
|
-
id: 'UUID required',
|
|
1322
|
-
[`${targetModel.toLowerCase()}Id`]: 'UUID required',
|
|
1323
|
-
[`${targetModel.toLowerCase()}Name`]: 'String optional',
|
|
1324
|
-
timestamp: 'DateTime required',
|
|
1325
|
-
createdBy: 'UUID required'
|
|
1326
|
-
}
|
|
1327
|
-
};
|
|
1328
|
-
// Add name attribute if model has one
|
|
1329
|
-
if (context.model?.attributes?.some((attr) => attr.name === 'name')) {
|
|
1330
|
-
events[`${modelName}CreatedIn${targetModel}`].attributes.name = 'String required';
|
|
1331
|
-
}
|
|
1332
|
-
// Child removed from parent
|
|
1333
|
-
events[`${modelName}RemovedFrom${targetModel}`] = {
|
|
1334
|
-
description: `${modelName} was removed from ${targetModel}`,
|
|
1335
|
-
attributes: {
|
|
1336
|
-
id: 'UUID required',
|
|
1337
|
-
[`${targetModel.toLowerCase()}Id`]: 'UUID required',
|
|
1338
|
-
[`${targetModel.toLowerCase()}Name`]: 'String optional',
|
|
1339
|
-
removedBy: 'UUID required',
|
|
1340
|
-
timestamp: 'DateTime required',
|
|
1341
|
-
reason: 'String optional'
|
|
1342
|
-
}
|
|
1343
|
-
};
|
|
1344
|
-
});
|
|
1345
|
-
}
|
|
1346
|
-
// Child relationship events
|
|
1347
|
-
if (context.relationships?.childRelationships) {
|
|
1348
|
-
context.relationships.childRelationships.forEach((rel) => {
|
|
1349
|
-
const targetModel = rel.targetModel || rel.name;
|
|
1350
|
-
const relType = rel.type || 'hasMany';
|
|
1351
|
-
// Child added to parent
|
|
1352
|
-
events[`${targetModel}AddedTo${modelName}`] = {
|
|
1353
|
-
description: `${targetModel} was added to ${modelName}`,
|
|
1354
|
-
attributes: {
|
|
1355
|
-
[`${modelName.toLowerCase()}Id`]: 'UUID required',
|
|
1356
|
-
[`${targetModel.toLowerCase()}Id`]: 'UUID required',
|
|
1357
|
-
relationshipType: `String default=${relType}`,
|
|
1358
|
-
addedBy: 'UUID required',
|
|
1359
|
-
timestamp: 'DateTime required'
|
|
1360
|
-
}
|
|
1361
|
-
};
|
|
1362
|
-
// Child removed from parent
|
|
1363
|
-
const removeEvent = {
|
|
1364
|
-
description: `${targetModel} was removed from ${modelName}`,
|
|
1365
|
-
attributes: {
|
|
1366
|
-
[`${modelName.toLowerCase()}Id`]: 'UUID required',
|
|
1367
|
-
[`${targetModel.toLowerCase()}Id`]: 'UUID required',
|
|
1368
|
-
relationshipType: `String default=${relType}`,
|
|
1369
|
-
removedBy: 'UUID required',
|
|
1370
|
-
timestamp: 'DateTime required'
|
|
1371
|
-
}
|
|
1372
|
-
};
|
|
1373
|
-
// Add cascade flag if specified
|
|
1374
|
-
if (rel.cascadeDelete) {
|
|
1375
|
-
removeEvent.attributes.cascadeTriggered = 'Boolean default=true';
|
|
1376
|
-
}
|
|
1377
|
-
events[`${targetModel}RemovedFrom${modelName}`] = removeEvent;
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
// Many-to-many relationship events
|
|
1381
|
-
if (context.relationships?.manyToManyRelationships) {
|
|
1382
|
-
context.relationships.manyToManyRelationships.forEach((rel) => {
|
|
1383
|
-
const targetModel = rel.targetModel;
|
|
1384
|
-
// Association created
|
|
1385
|
-
events[`${modelName}AssociatedWith${targetModel}`] = {
|
|
1386
|
-
description: `${modelName} was associated with ${targetModel}`,
|
|
1387
|
-
attributes: {
|
|
1388
|
-
[`${modelName.toLowerCase()}Id`]: 'UUID required',
|
|
1389
|
-
[`${targetModel.toLowerCase()}Id`]: 'UUID required',
|
|
1390
|
-
associationType: 'String default=manyToMany',
|
|
1391
|
-
associationData: 'Object optional',
|
|
1392
|
-
associatedBy: 'UUID required',
|
|
1393
|
-
timestamp: 'DateTime required'
|
|
1394
|
-
}
|
|
1395
|
-
};
|
|
1396
|
-
// Association removed
|
|
1397
|
-
events[`${modelName}DisassociatedFrom${targetModel}`] = {
|
|
1398
|
-
description: `${modelName} was disassociated from ${targetModel}`,
|
|
1399
|
-
attributes: {
|
|
1400
|
-
[`${modelName.toLowerCase()}Id`]: 'UUID required',
|
|
1401
|
-
[`${targetModel.toLowerCase()}Id`]: 'UUID required',
|
|
1402
|
-
associationType: 'String default=manyToMany',
|
|
1403
|
-
disassociatedBy: 'UUID required',
|
|
1404
|
-
timestamp: 'DateTime required',
|
|
1405
|
-
reason: 'String optional'
|
|
1406
|
-
}
|
|
1407
|
-
};
|
|
1408
|
-
});
|
|
1409
|
-
}
|
|
1410
|
-
return events;
|
|
1411
|
-
}
|
|
1412
543
|
}
|
|
1413
544
|
//# sourceMappingURL=rule-engine.js.map
|