@auto-engineer/narrative 1.131.0 → 1.135.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/package.json CHANGED
@@ -26,9 +26,9 @@
26
26
  "typescript": "^5.9.2",
27
27
  "zod": "^3.22.4",
28
28
  "zod-to-json-schema": "^3.22.3",
29
- "@auto-engineer/file-store": "1.131.0",
30
- "@auto-engineer/id": "1.131.0",
31
- "@auto-engineer/message-bus": "1.131.0"
29
+ "@auto-engineer/file-store": "1.135.0",
30
+ "@auto-engineer/id": "1.135.0",
31
+ "@auto-engineer/message-bus": "1.135.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^20.0.0",
@@ -38,7 +38,7 @@
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "version": "1.131.0",
41
+ "version": "1.135.0",
42
42
  "scripts": {
43
43
  "build": "tsx scripts/build.ts",
44
44
  "test": "vitest run --reporter=dot",
@@ -1,17 +1,29 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import type { Journey, JourneyPlanning, SceneClassification, SceneRoute } from './schema';
2
+ import type {
3
+ ComponentDefinition,
4
+ Journey,
5
+ JourneyPlanning,
6
+ RegionEntry,
7
+ SceneClassification,
8
+ SceneRoute,
9
+ UI,
10
+ } from './schema';
3
11
  import {
4
12
  CommandSliceSchema,
13
+ ComponentDefinitionSchema,
5
14
  DataSchema,
6
15
  DataTargetSchema,
16
+ DesignSchema,
7
17
  JourneyPlanningSchema,
8
18
  JourneySchema,
9
19
  modelSchema,
10
20
  NarrativeNamesOnlySchema,
11
21
  NarrativeSchema,
12
22
  QuerySliceSchema,
23
+ RegionEntrySchema,
13
24
  SceneClassificationSchema,
14
25
  SceneRouteSchema,
26
+ UISchema,
15
27
  } from './schema';
16
28
 
17
29
  describe('CommandSliceSchema', () => {
@@ -527,3 +539,212 @@ describe('QuerySliceSchema', () => {
527
539
  }
528
540
  });
529
541
  });
542
+
543
+ describe('RegionEntrySchema', () => {
544
+ it('should accept valid region entry with required fields', () => {
545
+ const result = RegionEntrySchema.safeParse({ id: 'hero-1', name: 'HeroBanner' });
546
+ expect(result.success).toBe(true);
547
+ if (result.success) {
548
+ expect(result.data).toEqual({ id: 'hero-1', name: 'HeroBanner' });
549
+ }
550
+ });
551
+
552
+ it('should accept optional slots', () => {
553
+ const result = RegionEntrySchema.safeParse({ id: 'card-1', name: 'Card', slots: { title: 'Hello' } });
554
+ expect(result.success).toBe(true);
555
+ if (result.success) {
556
+ expect(result.data.slots).toEqual({ title: 'Hello' });
557
+ }
558
+ });
559
+
560
+ it('should reject missing id', () => {
561
+ const result = RegionEntrySchema.safeParse({ name: 'Card' });
562
+ expect(result.success).toBe(false);
563
+ });
564
+
565
+ it('should reject missing name', () => {
566
+ const result = RegionEntrySchema.safeParse({ id: 'card-1' });
567
+ expect(result.success).toBe(false);
568
+ });
569
+ });
570
+
571
+ describe('UISchema', () => {
572
+ it('should accept valid UI with required fields', () => {
573
+ const result = UISchema.safeParse({
574
+ layoutId: 'two-column',
575
+ mode: 'as-is',
576
+ regions: { main: [{ id: 'c-1', name: 'List' }] },
577
+ });
578
+ expect(result.success).toBe(true);
579
+ if (result.success) {
580
+ expect(result.data).toEqual({
581
+ layoutId: 'two-column',
582
+ mode: 'as-is',
583
+ regions: { main: [{ id: 'c-1', name: 'List' }] },
584
+ });
585
+ }
586
+ });
587
+
588
+ it('should accept all valid mode values', () => {
589
+ for (const mode of ['as-is', 'modify', 'custom'] as const) {
590
+ const result = UISchema.safeParse({ layoutId: 'l', mode, regions: {} });
591
+ expect(result.success).toBe(true);
592
+ }
593
+ });
594
+
595
+ it('should reject invalid mode', () => {
596
+ const result = UISchema.safeParse({ layoutId: 'l', mode: 'invalid', regions: {} });
597
+ expect(result.success).toBe(false);
598
+ });
599
+
600
+ it('should accept optional customizationNotes', () => {
601
+ const result = UISchema.safeParse({
602
+ layoutId: 'l',
603
+ mode: 'modify',
604
+ regions: {},
605
+ customizationNotes: 'Make header sticky',
606
+ });
607
+ expect(result.success).toBe(true);
608
+ if (result.success) {
609
+ expect(result.data.customizationNotes).toBe('Make header sticky');
610
+ }
611
+ });
612
+
613
+ it('should reject missing layoutId', () => {
614
+ const result = UISchema.safeParse({ mode: 'as-is', regions: {} });
615
+ expect(result.success).toBe(false);
616
+ });
617
+
618
+ it('should reject missing mode', () => {
619
+ const result = UISchema.safeParse({ layoutId: 'l', regions: {} });
620
+ expect(result.success).toBe(false);
621
+ });
622
+
623
+ it('should reject missing regions', () => {
624
+ const result = UISchema.safeParse({ layoutId: 'l', mode: 'as-is' });
625
+ expect(result.success).toBe(false);
626
+ });
627
+ });
628
+
629
+ describe('ComponentDefinitionSchema', () => {
630
+ it('should accept valid component definition', () => {
631
+ const result = ComponentDefinitionSchema.safeParse({
632
+ id: 'comp-1',
633
+ name: 'PriceTag',
634
+ category: 'commerce',
635
+ description: 'Displays a price',
636
+ slots: { price: { type: 'number' } },
637
+ template: '<span>{{price}}</span>',
638
+ });
639
+ expect(result.success).toBe(true);
640
+ if (result.success) {
641
+ expect(result.data).toEqual({
642
+ id: 'comp-1',
643
+ name: 'PriceTag',
644
+ category: 'commerce',
645
+ description: 'Displays a price',
646
+ slots: { price: { type: 'number' } },
647
+ template: '<span>{{price}}</span>',
648
+ });
649
+ }
650
+ });
651
+
652
+ it('should reject missing required fields', () => {
653
+ const result = ComponentDefinitionSchema.safeParse({ id: 'comp-1', name: 'PriceTag' });
654
+ expect(result.success).toBe(false);
655
+ });
656
+ });
657
+
658
+ describe('DesignSchema ui field', () => {
659
+ it('should accept design with optional ui', () => {
660
+ const result = DesignSchema.safeParse({
661
+ ui: {
662
+ layoutId: 'sidebar',
663
+ mode: 'as-is',
664
+ regions: { sidebar: [{ id: 'nav', name: 'NavMenu' }] },
665
+ },
666
+ });
667
+ expect(result.success).toBe(true);
668
+ if (result.success) {
669
+ expect(result.data.ui?.layoutId).toBe('sidebar');
670
+ }
671
+ });
672
+
673
+ it('should accept design without ui', () => {
674
+ const result = DesignSchema.safeParse({});
675
+ expect(result.success).toBe(true);
676
+ if (result.success) {
677
+ expect(result.data.ui).toBeUndefined();
678
+ }
679
+ });
680
+ });
681
+
682
+ describe('modelSchema design.components field', () => {
683
+ const minimalModel = {
684
+ variant: 'specs' as const,
685
+ narratives: [],
686
+ messages: [],
687
+ modules: [],
688
+ };
689
+
690
+ it('should accept model with design.components', () => {
691
+ const result = modelSchema.safeParse({
692
+ ...minimalModel,
693
+ design: {
694
+ components: [
695
+ {
696
+ id: 'comp-1',
697
+ name: 'PriceTag',
698
+ category: 'commerce',
699
+ description: 'Displays a price',
700
+ slots: { price: { type: 'number' } },
701
+ template: '<span>{{price}}</span>',
702
+ },
703
+ ],
704
+ },
705
+ });
706
+ expect(result.success).toBe(true);
707
+ if (result.success) {
708
+ expect(result.data.design?.components).toHaveLength(1);
709
+ expect(result.data.design?.components?.[0].name).toBe('PriceTag');
710
+ }
711
+ });
712
+
713
+ it('should accept model design without components', () => {
714
+ const result = modelSchema.safeParse({
715
+ ...minimalModel,
716
+ design: {},
717
+ });
718
+ expect(result.success).toBe(true);
719
+ if (result.success) {
720
+ expect(result.data.design?.components).toBeUndefined();
721
+ }
722
+ });
723
+ });
724
+
725
+ describe('exported UI types', () => {
726
+ it('RegionEntry type matches RegionEntrySchema inference', () => {
727
+ const parsed = RegionEntrySchema.parse({ id: 'r-1', name: 'Header' });
728
+ const typed: RegionEntry = parsed;
729
+ expect(typed).toEqual({ id: 'r-1', name: 'Header' });
730
+ });
731
+
732
+ it('UI type matches UISchema inference', () => {
733
+ const parsed = UISchema.parse({ layoutId: 'l', mode: 'as-is', regions: {} });
734
+ const typed: UI = parsed;
735
+ expect(typed).toEqual({ layoutId: 'l', mode: 'as-is', regions: {} });
736
+ });
737
+
738
+ it('ComponentDefinition type matches ComponentDefinitionSchema inference', () => {
739
+ const parsed = ComponentDefinitionSchema.parse({
740
+ id: 'c-1',
741
+ name: 'Btn',
742
+ category: 'ui',
743
+ description: 'A button',
744
+ slots: {},
745
+ template: '<button/>',
746
+ });
747
+ const typed: ComponentDefinition = parsed;
748
+ expect(typed.name).toBe('Btn');
749
+ });
750
+ });
package/src/schema.ts CHANGED
@@ -207,6 +207,7 @@ export const MappingEntrySchema = z
207
207
  .object({
208
208
  source: MappingFieldRefSchema.describe('Source field reference'),
209
209
  target: MappingFieldRefSchema.describe('Target field reference'),
210
+ operator: z.enum(['eq', 'ne', 'gt', 'gte', 'lt', 'lte']).optional(),
210
211
  })
211
212
  .describe('Mapping entry linking a source field to a target field');
212
213
 
@@ -217,10 +218,39 @@ export const ImageAssetSchema = z
217
218
  })
218
219
  .describe('Image asset with optional generation metadata');
219
220
 
221
+ const RegionEntrySchema = z
222
+ .object({
223
+ id: z.string(),
224
+ name: z.string(),
225
+ slots: z.record(z.unknown()).optional(),
226
+ })
227
+ .describe('Component placed in a layout region');
228
+
229
+ const UISchema = z
230
+ .object({
231
+ layoutId: z.string(),
232
+ mode: z.enum(['as-is', 'modify', 'custom']),
233
+ regions: z.record(z.array(RegionEntrySchema)),
234
+ customizationNotes: z.string().optional(),
235
+ })
236
+ .describe('UI composition for a slice');
237
+
238
+ const ComponentDefinitionSchema = z
239
+ .object({
240
+ id: z.string(),
241
+ name: z.string(),
242
+ category: z.string(),
243
+ description: z.string(),
244
+ slots: z.record(z.unknown()),
245
+ template: z.string(),
246
+ })
247
+ .describe('Custom reusable component definition');
248
+
220
249
  export const DesignSchema = z
221
250
  .object({
222
251
  imageAsset: ImageAssetSchema.optional().describe('Primary image asset for this entity'),
223
252
  metadata: z.record(z.unknown()).optional().describe('Flexible design metadata'),
253
+ ui: UISchema.optional().describe('UI composition for this entity'),
224
254
  })
225
255
  .describe('Design fields for visual representation');
226
256
 
@@ -512,6 +542,10 @@ export const ClientServerNamesSchema = z
512
542
  })
513
543
  .describe('System with client/server descriptions for behavior planning');
514
544
 
545
+ const ModelDesignSchema = DesignSchema.extend({
546
+ components: z.array(ComponentDefinitionSchema).optional().describe('Custom reusable component definitions'),
547
+ }).describe('Model-level design fields with component definitions');
548
+
515
549
  export const modelSchema = z
516
550
  .object({
517
551
  variant: z.literal('specs').describe('Full specification with all details'),
@@ -520,7 +554,7 @@ export const modelSchema = z
520
554
  integrations: z.array(IntegrationSchema).optional(),
521
555
  modules: z.array(ModuleSchema).describe('Modules for type ownership and file grouping'),
522
556
  journeys: z.array(JourneySchema).optional(),
523
- design: DesignSchema.optional().describe('Design fields for visual representation'),
557
+ design: ModelDesignSchema.optional().describe('Design fields for visual representation'),
524
558
  })
525
559
  .describe('Complete system specification with all implementation details');
526
560
 
@@ -550,6 +584,9 @@ export {
550
584
  StepErrorSchema,
551
585
  StepWithDocStringSchema,
552
586
  StepWithErrorSchema,
587
+ RegionEntrySchema,
588
+ UISchema,
589
+ ComponentDefinitionSchema,
553
590
  };
554
591
 
555
592
  export type Model = z.infer<typeof modelSchema>;
@@ -576,3 +613,6 @@ export type SceneRoute = z.infer<typeof SceneRouteSchema>;
576
613
  export type JourneyPlanning = z.infer<typeof JourneyPlanningSchema>;
577
614
  export type ImageAsset = z.infer<typeof ImageAssetSchema>;
578
615
  export type Design = z.infer<typeof DesignSchema>;
616
+ export type RegionEntry = z.infer<typeof RegionEntrySchema>;
617
+ export type UI = z.infer<typeof UISchema>;
618
+ export type ComponentDefinition = z.infer<typeof ComponentDefinitionSchema>;