@zodic/shared 0.0.372 → 0.0.374

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.
@@ -28,42 +28,42 @@ export class ArchetypeService {
28
28
  message: string,
29
29
  context: Record<string, any> = {}
30
30
  ) {
31
- const logMessage = `[${level.toUpperCase()}] ${message}`;
32
-
33
- console.log(logMessage, context);
34
- // try {
35
- // await db
36
- // .insert(schema.logs)
37
- // .values({
38
- // id: logId,
39
- // level,
40
- // message,
41
- // context: JSON.stringify(context),
42
- // createdAt: new Date().getTime(),
43
- // })
44
- // .execute();
45
- // } catch (error) {
46
- // console.error('[ERROR] Failed to persist log to database:', {
47
- // error,
48
- // logId,
49
- // message,
50
- // context,
51
- // });
52
- // }
31
+ const logMessage = `[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}`;
32
+
33
+ console[level](logMessage, context);
53
34
  }
54
35
 
55
- // Replace generateArchetypeNames and generateArchetypeNamesBatch
56
36
  async generateArchetypeNames(
57
37
  combinationString: string,
58
- overrideExisting: boolean = false,
38
+ override: boolean = false,
59
39
  indexesToGenerate: number[] = [1, 2, 3]
60
40
  ) {
61
41
  await this.log('info', 'Starting generateArchetypeNames', {
62
42
  combinationString,
63
- overrideExisting,
43
+ override,
64
44
  indexesToGenerate,
65
45
  });
66
46
 
47
+ const db = this.context.drizzle();
48
+ const existingArchetypes = await db
49
+ .select()
50
+ .from(schema.archetypesData)
51
+ .where(eq(schema.archetypesData.combination, combinationString))
52
+ .orderBy(schema.archetypesData.archetypeIndex)
53
+ .execute();
54
+
55
+ if (
56
+ !override &&
57
+ existingArchetypes.length >= 3 * 2 * 2 && // 3 indexes * 2 genders * 2 languages
58
+ existingArchetypes.every((a) => a.name && a.essenceLine)
59
+ ) {
60
+ await this.log('info', 'Skipping archetype names generation', {
61
+ reason: 'All archetypes have names and essence lines',
62
+ existingCount: existingArchetypes.length,
63
+ });
64
+ return;
65
+ }
66
+
67
67
  const [sun, ascendant, moon] = combinationString.split(
68
68
  '-'
69
69
  ) as ZodiacSignSlug[];
@@ -95,8 +95,6 @@ export class ArchetypeService {
95
95
  portugueseVariants,
96
96
  });
97
97
 
98
- const db = this.context.drizzle();
99
-
100
98
  async function isEnglishNameDuplicate(name: string): Promise<boolean> {
101
99
  const result = await db
102
100
  .select({ name: schema.archetypesData.name })
@@ -121,7 +119,10 @@ export class ArchetypeService {
121
119
  await this.log(
122
120
  'debug',
123
121
  `Skipping index ${index} as it is not in indexesToGenerate`,
124
- { index, indexesToGenerate }
122
+ {
123
+ index,
124
+ indexesToGenerate,
125
+ }
125
126
  );
126
127
  continue;
127
128
  }
@@ -134,7 +135,7 @@ export class ArchetypeService {
134
135
  continue;
135
136
  }
136
137
 
137
- for (const gender of ['male', 'female']) {
138
+ for (const gender of ['male', 'female'] as Gender[]) {
138
139
  for (const lang of ['en-us', 'pt-br']) {
139
140
  const id = `${combinationString}:${gender}:${index}${
140
141
  lang === 'pt-br' ? ':pt' : ''
@@ -148,9 +149,22 @@ export class ArchetypeService {
148
149
  const essenceLine =
149
150
  lang === 'en-us' ? entry.essenceLine : ptVariant.essenceLine;
150
151
 
152
+ const existing = existingArchetypes.find((a) => a.id === id);
153
+ if (existing && !override && existing.name && existing.essenceLine) {
154
+ await this.log('debug', `Skipping existing archetype ${id}`, {
155
+ name: existing.name,
156
+ essenceLine: existing.essenceLine,
157
+ archetypeIndex: index,
158
+ });
159
+ continue;
160
+ }
161
+
151
162
  await this.log('debug', `Saving archetype name for ${id}`, {
152
163
  name,
153
164
  essenceLine,
165
+ archetypeIndex: index,
166
+ gender,
167
+ language: lang,
154
168
  });
155
169
 
156
170
  await db
@@ -171,6 +185,7 @@ export class ArchetypeService {
171
185
  name,
172
186
  essenceLine,
173
187
  updatedAt: new Date().getTime(),
188
+ status: 'idle',
174
189
  },
175
190
  });
176
191
 
@@ -200,6 +215,13 @@ export class ArchetypeService {
200
215
  blocksCount: blocks.length,
201
216
  });
202
217
 
218
+ if (blocks.length !== 3) {
219
+ this.log('error', 'Expected exactly 3 archetype blocks', {
220
+ blocksCount: blocks.length,
221
+ });
222
+ throw new Error(`Expected 3 archetype blocks, got ${blocks.length}`);
223
+ }
224
+
203
225
  const english: Array<{ name: string; essenceLine: string }> = [];
204
226
  const portuguese: Array<{
205
227
  masc: string;
@@ -207,7 +229,7 @@ export class ArchetypeService {
207
229
  essenceLine: string;
208
230
  }> = [];
209
231
 
210
- for (const block of blocks) {
232
+ blocks.forEach((block, index) => {
211
233
  const nameMatch = block.match(/- Name: (.+)/);
212
234
  const essenceLineMatch = block.match(/- Essence Line: (.+)/);
213
235
  const ptMascMatch = block.match(/- Portuguese \(Masc\): (.+)/);
@@ -230,10 +252,21 @@ export class ArchetypeService {
230
252
  fem: ptFemMatch[1].trim(),
231
253
  essenceLine: ptEssenceLineMatch[1].trim(),
232
254
  });
255
+ this.log('debug', `Parsed block for archetypeIndex ${index + 1}`, {
256
+ english: english[index],
257
+ portuguese: portuguese[index],
258
+ });
233
259
  } else {
234
- this.log('warn', 'Malformed archetype name block', { block });
260
+ this.log(
261
+ 'error',
262
+ `Malformed archetype name block for index ${index + 1}`,
263
+ { block }
264
+ );
265
+ throw new Error(
266
+ `Malformed archetype name block for index ${index + 1}`
267
+ );
235
268
  }
236
- }
269
+ });
237
270
 
238
271
  this.log('info', 'Completed parseArchetypeNameBlocks', {
239
272
  english,
@@ -244,42 +277,82 @@ export class ArchetypeService {
244
277
 
245
278
  async generateDescriptionsAndVirtues(
246
279
  combinationString: string,
247
- gender: Gender,
248
- language: string
280
+ gender: Gender, // Used for compatibility, but processes both genders
281
+ language: string,
282
+ override: boolean = false
249
283
  ): Promise<ParsedDescriptionAndVirtues[]> {
250
284
  await this.log('info', 'Starting generateDescriptionsAndVirtues', {
251
285
  combinationString,
252
286
  gender,
253
287
  language,
288
+ override,
254
289
  });
255
290
 
256
291
  const db = this.context.drizzle();
257
- const archetypes = await db
258
- .select()
259
- .from(schema.archetypesData)
260
- .where(
261
- and(
262
- eq(schema.archetypesData.combination, combinationString),
263
- eq(schema.archetypesData.gender, gender),
264
- eq(schema.archetypesData.language, 'en-us')
292
+ const allGenders: Gender[] = ['male', 'female'];
293
+ const archetypesByGender: Record<Gender, any[]> = { male: [], female: [] };
294
+
295
+ for (const g of allGenders) {
296
+ const archetypes = await db
297
+ .select()
298
+ .from(schema.archetypesData)
299
+ .where(
300
+ and(
301
+ eq(schema.archetypesData.combination, combinationString),
302
+ eq(schema.archetypesData.gender, g),
303
+ eq(schema.archetypesData.language, language)
304
+ )
265
305
  )
266
- )
267
- .execute();
306
+ .orderBy(schema.archetypesData.archetypeIndex)
307
+ .execute();
308
+ archetypesByGender[g] = archetypes;
309
+ }
310
+
311
+ const needsGeneration = allGenders.some(
312
+ (g) =>
313
+ archetypesByGender[g].length !== 3 ||
314
+ archetypesByGender[g].some((a) => !a.description || a.virtues === '[]')
315
+ );
316
+
317
+ if (!override && !needsGeneration) {
318
+ await this.log('info', 'Skipping descriptions and virtues generation', {
319
+ reason: 'Descriptions and virtues already exist for all archetypes',
320
+ });
321
+ return archetypesByGender['male'].map((a, i) => ({
322
+ descriptionEN: a.description || '',
323
+ descriptionPTM: archetypesByGender['male'][i].description || '',
324
+ descriptionPTF: archetypesByGender['female'][i].description || '',
325
+ virtuesEN: JSON.parse(a.virtues || '[]'),
326
+ virtuesPT: JSON.parse(a.virtues || '[]'),
327
+ }));
328
+ }
268
329
 
269
330
  await this.log('info', 'Fetched archetypes for description generation', {
270
- archetypesCount: archetypes.length,
271
- names: archetypes.map((a) => a.name),
331
+ maleArchetypes: archetypesByGender['male'].map((a) => ({
332
+ id: a.id,
333
+ name: a.name,
334
+ index: a.archetypeIndex,
335
+ })),
336
+ femaleArchetypes: archetypesByGender['female'].map((a) => ({
337
+ id: a.id,
338
+ name: a.name,
339
+ index: a.archetypeIndex,
340
+ })),
272
341
  });
273
342
 
274
343
  const descVirtueMessages = this.context
275
344
  .buildLLMMessages()
276
345
  .generateCosmicMirrorDescriptionAndVirtues({
277
346
  combination: combinationString,
278
- names: [archetypes[0].name, archetypes[1].name, archetypes[2].name],
279
- essenceLines: [
280
- archetypes[0].essenceLine,
281
- archetypes[1].essenceLine,
282
- archetypes[2].essenceLine,
347
+ names: archetypesByGender['male'].map((a) => a.name) as [
348
+ string,
349
+ string,
350
+ string
351
+ ],
352
+ essenceLines: archetypesByGender['male'].map((a) => a.essenceLine) as [
353
+ string,
354
+ string,
355
+ string
283
356
  ],
284
357
  });
285
358
 
@@ -289,9 +362,7 @@ export class ArchetypeService {
289
362
  const descVirtueResponse = await this.context
290
363
  .api()
291
364
  .callTogether.single(descVirtueMessages, {
292
- options: {
293
- temperature: 0.5,
294
- },
365
+ options: { temperature: 0.3 },
295
366
  });
296
367
  if (!descVirtueResponse) {
297
368
  await this.log('error', 'No response for descriptions and virtues', {
@@ -313,87 +384,79 @@ export class ArchetypeService {
313
384
 
314
385
  for (let i = 0; i < 3; i++) {
315
386
  const index = (i + 1).toString();
316
-
317
- // Update English entry (en-us)
318
- const enId = `${combinationString}:${gender}:${index}`;
319
- await this.log('debug', `Updating description and virtues for ${enId}`, {
320
- description: parsedDescVirtues[i].descriptionEN,
321
- virtues: parsedDescVirtues[i].virtuesEN,
322
- });
323
- await db
324
- .update(schema.archetypesData)
325
- .set({
326
- description: parsedDescVirtues[i].descriptionEN,
327
- virtues: JSON.stringify(parsedDescVirtues[i].virtuesEN),
328
- updatedAt: new Date().getTime(),
329
- })
330
- .where(
331
- and(
332
- eq(schema.archetypesData.id, enId),
333
- eq(schema.archetypesData.language, 'en-us')
334
- )
335
- )
336
- .execute();
337
- await this.log('info', `Updated description and virtues for ${enId}`);
338
-
339
- // Update Portuguese entries (pt-br) for both male and female
340
- const ptIdMale = `${combinationString}:${gender}:${index}:pt`;
341
- await this.log(
342
- 'debug',
343
- `Updating description and virtues for ${ptIdMale} (male)`,
344
- {
345
- description: parsedDescVirtues[i].descriptionPTM,
346
- virtues: parsedDescVirtues[i].virtuesPT,
387
+ for (const g of allGenders) {
388
+ const archetype = archetypesByGender[g][i];
389
+ if (!archetype || archetype.archetypeIndex !== index) {
390
+ await this.log(
391
+ 'error',
392
+ `Archetype index mismatch for gender ${g} at position ${i + 1}`,
393
+ {
394
+ expectedIndex: index,
395
+ actualIndex: archetype?.archetypeIndex,
396
+ archetypeId: archetype?.id,
397
+ }
398
+ );
399
+ throw new Error(
400
+ `Archetype index mismatch for gender ${g}: expected ${index}, got ${archetype?.archetypeIndex}`
401
+ );
347
402
  }
348
- );
349
- await db
350
- .update(schema.archetypesData)
351
- .set({
352
- description: parsedDescVirtues[i].descriptionPTM,
353
- virtues: JSON.stringify(parsedDescVirtues[i].virtuesPT),
354
- updatedAt: new Date().getTime(),
355
- })
356
- .where(
357
- and(
358
- eq(schema.archetypesData.id, ptIdMale),
359
- eq(schema.archetypesData.language, 'pt-br'),
360
- eq(schema.archetypesData.gender, 'male')
361
- )
362
- )
363
- .execute();
364
- await this.log(
365
- 'info',
366
- `Updated description and virtues for ${ptIdMale} (male)`
367
- );
368
403
 
369
- const ptIdFemale = `${combinationString}:${gender}:${index}:pt`;
370
- await this.log(
371
- 'debug',
372
- `Updating description and virtues for ${ptIdFemale} (female)`,
373
- {
374
- description: parsedDescVirtues[i].descriptionPTF,
375
- virtues: parsedDescVirtues[i].virtuesPT,
404
+ const id = `${combinationString}:${g}:${index}${
405
+ language === 'pt-br' ? ':pt' : ''
406
+ }`;
407
+ if (!override && archetype.description && archetype.virtues !== '[]') {
408
+ await this.log(
409
+ 'debug',
410
+ `Skipping existing description and virtues for ${id}`,
411
+ {
412
+ description: archetype.description,
413
+ virtues: archetype.virtues,
414
+ archetypeIndex: index,
415
+ gender: g,
416
+ language,
417
+ }
418
+ );
419
+ continue;
376
420
  }
377
- );
378
- await db
379
- .update(schema.archetypesData)
380
- .set({
381
- description: parsedDescVirtues[i].descriptionPTF,
382
- virtues: JSON.stringify(parsedDescVirtues[i].virtuesPT),
383
- updatedAt: new Date().getTime(),
384
- })
385
- .where(
386
- and(
387
- eq(schema.archetypesData.id, ptIdFemale),
388
- eq(schema.archetypesData.language, 'pt-br'),
389
- eq(schema.archetypesData.gender, 'female')
421
+
422
+ const description =
423
+ language === 'en-us'
424
+ ? parsedDescVirtues[i].descriptionEN
425
+ : g === 'male'
426
+ ? parsedDescVirtues[i].descriptionPTM
427
+ : parsedDescVirtues[i].descriptionPTF;
428
+ const virtues =
429
+ language === 'en-us'
430
+ ? parsedDescVirtues[i].virtuesEN
431
+ : parsedDescVirtues[i].virtuesPT;
432
+
433
+ await this.log('debug', `Updating description and virtues for ${id}`, {
434
+ description,
435
+ virtues,
436
+ archetypeIndex: index,
437
+ gender: g,
438
+ language,
439
+ });
440
+
441
+ await db
442
+ .update(schema.archetypesData)
443
+ .set({
444
+ description,
445
+ virtues: JSON.stringify(virtues),
446
+ updatedAt: new Date().getTime(),
447
+ })
448
+ .where(
449
+ and(
450
+ eq(schema.archetypesData.id, id),
451
+ eq(schema.archetypesData.language, language),
452
+ eq(schema.archetypesData.gender, g),
453
+ eq(schema.archetypesData.archetypeIndex, index)
454
+ )
390
455
  )
391
- )
392
- .execute();
393
- await this.log(
394
- 'info',
395
- `Updated description and virtues for ${ptIdFemale} (female)`
396
- );
456
+ .execute();
457
+
458
+ await this.log('info', `Updated description and virtues for ${id}`);
459
+ }
397
460
  }
398
461
 
399
462
  await this.log('info', 'Completed generateDescriptionsAndVirtues', {
@@ -442,6 +505,22 @@ export class ArchetypeService {
442
505
  lines,
443
506
  });
444
507
 
508
+ if (lines[0] !== `${entryIndex + 1}.`) {
509
+ this.log(
510
+ 'error',
511
+ `Invalid archetype index in entry ${entryIndex + 1}`,
512
+ {
513
+ expected: `${entryIndex + 1}.`,
514
+ found: lines[0],
515
+ }
516
+ );
517
+ throw new Error(
518
+ `Invalid archetype index in entry ${entryIndex + 1}: expected ${
519
+ entryIndex + 1
520
+ }, got ${lines[0]}`
521
+ );
522
+ }
523
+
445
524
  let descriptionEN = '';
446
525
  let descriptionPTM = '';
447
526
  let descriptionPTF = '';
@@ -449,20 +528,10 @@ export class ArchetypeService {
449
528
  let virtuesPT: string[] = [];
450
529
  let currentField = '';
451
530
 
452
- for (let i = 0; i < lines.length; i++) {
531
+ for (let i = 1; i < lines.length; i++) {
453
532
  let line = lines[i];
454
-
455
- // Remove markdown bold (**...**) from field names
456
533
  line = line.replace(/\*\*(.*?)\*\*/g, '$1');
457
534
 
458
- if (
459
- line.startsWith('1.') ||
460
- line.startsWith('2.') ||
461
- line.startsWith('3.')
462
- ) {
463
- continue; // Skip entry number lines
464
- }
465
-
466
535
  if (line.startsWith('• Description EN:')) {
467
536
  currentField = 'descriptionEN';
468
537
  descriptionEN = line.split('• Description EN:')[1]?.trim() || '';
@@ -510,7 +579,6 @@ export class ArchetypeService {
510
579
  virtuesPT,
511
580
  });
512
581
  } else if (line.startsWith('• Virtues:')) {
513
- // Fallback for older response format: use English virtues and attempt to translate
514
582
  currentField = 'virtuesEN';
515
583
  virtuesEN = line
516
584
  .split('• Virtues:')[1]
@@ -524,14 +592,12 @@ export class ArchetypeService {
524
592
  { virtuesEN }
525
593
  );
526
594
 
527
- // Simple translation mapping for common virtues (as a fallback)
528
595
  const virtueTranslations: { [key: string]: string } = {
529
596
  Harmony: 'Harmonia',
530
597
  Intuition: 'Intuição',
531
598
  Grace: 'Graça',
532
- // Add more translations as needed
533
599
  };
534
- virtuesPT = virtuesEN.map((v) => virtueTranslations[v] || v); // Fallback to English if no translation
600
+ virtuesPT = virtuesEN.map((v) => virtueTranslations[v] || v);
535
601
  this.log(
536
602
  'debug',
537
603
  `Generated virtuesPT (fallback) for entry ${entryIndex + 1}`,
@@ -563,7 +629,6 @@ export class ArchetypeService {
563
629
  }
564
630
  }
565
631
 
566
- // Validate the parsed data
567
632
  if (
568
633
  !descriptionEN ||
569
634
  !descriptionPTM ||
@@ -572,7 +637,7 @@ export class ArchetypeService {
572
637
  virtuesPT.length !== 3
573
638
  ) {
574
639
  this.log(
575
- 'warn',
640
+ 'error',
576
641
  `Malformed description and virtues response for entry ${
577
642
  entryIndex + 1
578
643
  }`,
@@ -592,13 +657,19 @@ export class ArchetypeService {
592
657
  );
593
658
  }
594
659
 
595
- return {
660
+ const parsed = {
596
661
  descriptionEN,
597
662
  descriptionPTM,
598
663
  descriptionPTF,
599
664
  virtuesEN,
600
665
  virtuesPT,
601
666
  };
667
+ this.log(
668
+ 'debug',
669
+ `Parsed description and virtues for archetypeIndex ${entryIndex + 1}`,
670
+ { parsed }
671
+ );
672
+ return parsed;
602
673
  });
603
674
 
604
675
  this.log('info', 'Completed parseDescriptionAndVirtuesResponse', {
@@ -609,85 +680,156 @@ export class ArchetypeService {
609
680
 
610
681
  async generateContent(
611
682
  combinationString: string,
612
- gender: Gender,
683
+ gender: Gender, // Used for compatibility, but processes both genders
613
684
  language: string,
614
- descriptions: Array<{ descriptionEN: string }>
685
+ descriptions: Array<{ descriptionEN: string }>,
686
+ override: boolean = false
615
687
  ) {
616
688
  await this.log('info', 'Starting generateContent', {
617
689
  combinationString,
618
690
  gender,
619
691
  language,
692
+ override,
620
693
  });
621
694
 
622
695
  const db = this.context.drizzle();
623
- const archetypes = await db
624
- .select()
625
- .from(schema.archetypesData)
626
- .where(
627
- and(
628
- eq(schema.archetypesData.combination, combinationString),
629
- eq(schema.archetypesData.gender, gender),
630
- eq(schema.archetypesData.language, 'en-us')
696
+ const allGenders: Gender[] = ['male', 'female'];
697
+ const archetypesByGender: Record<Gender, any[]> = { male: [], female: [] };
698
+
699
+ for (const g of allGenders) {
700
+ const archetypes = await db
701
+ .select()
702
+ .from(schema.archetypesData)
703
+ .where(
704
+ and(
705
+ eq(schema.archetypesData.combination, combinationString),
706
+ eq(schema.archetypesData.gender, g),
707
+ eq(schema.archetypesData.language, language)
708
+ )
631
709
  )
632
- )
633
- .execute();
710
+ .orderBy(schema.archetypesData.archetypeIndex)
711
+ .execute();
712
+ archetypesByGender[g] = archetypes;
713
+ }
714
+
715
+ const needsGeneration = allGenders.some(
716
+ (g) =>
717
+ archetypesByGender[g].length !== 3 ||
718
+ archetypesByGender[g].some((a) => a.content === '[]')
719
+ );
720
+
721
+ if (!override && !needsGeneration) {
722
+ await this.log('info', 'Skipping content generation', {
723
+ reason: 'Content already exists for all archetypes',
724
+ });
725
+ return;
726
+ }
634
727
 
635
728
  await this.log('info', 'Fetched archetypes for content generation', {
636
- archetypesCount: archetypes.length,
637
- names: archetypes.map((a) => a.name),
729
+ maleArchetypes: archetypesByGender['male'].map((a) => ({
730
+ id: a.id,
731
+ name: a.name,
732
+ index: a.archetypeIndex,
733
+ })),
734
+ femaleArchetypes: archetypesByGender['female'].map((a) => ({
735
+ id: a.id,
736
+ name: a.name,
737
+ index: a.archetypeIndex,
738
+ })),
638
739
  });
639
740
 
640
741
  const contentResults = [];
641
742
  for (let i = 0; i < 3; i++) {
642
- await this.log('debug', `Generating content for archetype ${i + 1}`, {
643
- name: archetypes[i].name,
644
- });
645
- const contentMessages = this.context
646
- .buildLLMMessages()
647
- .generateCosmicMirrorArchetypeContent({
648
- combination: combinationString,
649
- name: archetypes[i].name,
650
- description: descriptions[i].descriptionEN,
651
- });
743
+ const index = (i + 1).toString();
744
+ for (const g of allGenders) {
745
+ const archetype = archetypesByGender[g][i];
746
+ if (!archetype || archetype.archetypeIndex !== index) {
747
+ await this.log(
748
+ 'error',
749
+ `Archetype index mismatch for gender ${g} at position ${i + 1}`,
750
+ {
751
+ expectedIndex: index,
752
+ actualIndex: archetype?.archetypeIndex,
753
+ archetypeId: archetype?.id,
754
+ }
755
+ );
756
+ throw new Error(
757
+ `Archetype index mismatch for gender ${g}: expected ${index}, got ${archetype?.archetypeIndex}`
758
+ );
759
+ }
760
+
761
+ const id = `${combinationString}:${g}:${index}${
762
+ language === 'pt-br' ? ':pt' : ''
763
+ }`;
764
+ if (!override && archetype.content !== '[]') {
765
+ await this.log('debug', `Skipping existing content for ${id}`, {
766
+ content: archetype.content,
767
+ archetypeIndex: index,
768
+ gender: g,
769
+ language,
770
+ });
771
+ if (g === 'male') contentResults.push({}); // Placeholder for male only
772
+ continue;
773
+ }
652
774
 
653
- await this.log('debug', `Calling API for content of archetype ${i + 1}`, {
654
- messages: contentMessages,
655
- });
656
- const contentResponse = await this.context
657
- .api()
658
- .callTogether.single(contentMessages, {});
659
- if (!contentResponse) {
660
775
  await this.log(
661
- 'error',
662
- `No response for content of archetype ${i + 1}`,
663
- { combinationString, archetype: archetypes[i].name }
776
+ 'debug',
777
+ `Generating content for archetype ${index} (${g})`,
778
+ {
779
+ name: archetype.name,
780
+ }
664
781
  );
665
- throw new Error(
666
- `Failed to generate content for archetype ${
667
- i + 1
668
- } of: ${combinationString}`
782
+ const contentMessages = this.context
783
+ .buildLLMMessages()
784
+ .generateCosmicMirrorArchetypeContent({
785
+ combination: combinationString,
786
+ name: archetype.name,
787
+ description: descriptions[i].descriptionEN,
788
+ });
789
+
790
+ await this.log(
791
+ 'debug',
792
+ `Calling API for content of archetype ${index} (${g})`,
793
+ {
794
+ messages: contentMessages,
795
+ }
796
+ );
797
+ const contentResponse = await this.context
798
+ .api()
799
+ .callTogether.single(contentMessages, {});
800
+ if (!contentResponse) {
801
+ await this.log(
802
+ 'error',
803
+ `No response for content of archetype ${index} (${g})`,
804
+ {
805
+ combinationString,
806
+ archetype: archetype.name,
807
+ }
808
+ );
809
+ throw new Error(
810
+ `Failed to generate content for archetype ${index} (${g}) of: ${combinationString}`
811
+ );
812
+ }
813
+ await this.log(
814
+ 'info',
815
+ `Received content response for archetype ${index} (${g})`,
816
+ {
817
+ responseLength: contentResponse.length,
818
+ }
669
819
  );
670
- }
671
- await this.log(
672
- 'info',
673
- `Received content response for archetype ${i + 1}`,
674
- { responseLength: contentResponse.length }
675
- );
676
820
 
677
- await this.log(
678
- 'debug',
679
- `Parsing content response for archetype ${i + 1}`
680
- );
681
- const parsedContent = this.parseContentResponse(contentResponse);
682
- contentResults.push(parsedContent);
683
- await this.log('info', `Parsed content for archetype ${i + 1}`, {
684
- parsedContent,
685
- });
821
+ await this.log(
822
+ 'debug',
823
+ `Parsing content response for archetype ${index} (${g})`
824
+ );
825
+ const parsedContent = this.parseContentResponse(contentResponse);
826
+ if (g === 'male') contentResults.push(parsedContent);
827
+ await this.log('info', `Parsed content for archetype ${index} (${g})`, {
828
+ parsedContent,
829
+ });
686
830
 
687
- const index = (i + 1).toString();
688
- for (const lang of ['en-us', 'pt-br']) {
689
831
  const contentLangIndex =
690
- lang === 'en-us' ? 0 : gender === 'male' ? 1 : 2;
832
+ language === 'en-us' ? 0 : g === 'male' ? 1 : 2;
691
833
  const content = [
692
834
  {
693
835
  section: 'voiceOfTheSoul',
@@ -715,10 +857,12 @@ export class ArchetypeService {
715
857
  },
716
858
  ];
717
859
 
718
- const id = `${combinationString}:${gender}:${index}${
719
- lang === 'pt-br' ? ':pt' : ''
720
- }`;
721
- await this.log('debug', `Updating content for ${id}`, { content });
860
+ await this.log('debug', `Updating content for ${id}`, {
861
+ content,
862
+ archetypeIndex: index,
863
+ gender: g,
864
+ language,
865
+ });
722
866
 
723
867
  await db
724
868
  .update(schema.archetypesData)
@@ -729,7 +873,9 @@ export class ArchetypeService {
729
873
  .where(
730
874
  and(
731
875
  eq(schema.archetypesData.id, id),
732
- eq(schema.archetypesData.language, lang)
876
+ eq(schema.archetypesData.language, language),
877
+ eq(schema.archetypesData.gender, g),
878
+ eq(schema.archetypesData.archetypeIndex, index)
733
879
  )
734
880
  )
735
881
  .execute();
@@ -759,6 +905,14 @@ export class ArchetypeService {
759
905
  sectionsCount: sections.length,
760
906
  });
761
907
 
908
+ if (sections.length !== 3) {
909
+ this.log('error', 'Expected exactly 3 content sections', {
910
+ sectionsCount: sections.length,
911
+ response,
912
+ });
913
+ throw new Error(`Expected 3 content sections, got ${sections.length}`);
914
+ }
915
+
762
916
  const result = sections.map((section, sectionIndex) => {
763
917
  this.log('debug', `Processing section ${sectionIndex + 1}`, { section });
764
918
 
@@ -768,24 +922,27 @@ export class ArchetypeService {
768
922
  lines,
769
923
  });
770
924
 
925
+ const expectedHeader =
926
+ sectionIndex === 0 ? 'EN:' : sectionIndex === 1 ? 'PT-M:' : 'PT-F:';
927
+ if (lines[0] !== expectedHeader) {
928
+ this.log(
929
+ 'error',
930
+ `Invalid language header in section ${sectionIndex + 1}`,
931
+ {
932
+ expected: expectedHeader,
933
+ found: lines[0],
934
+ }
935
+ );
936
+ throw new Error(
937
+ `Invalid language header in section ${sectionIndex + 1}`
938
+ );
939
+ }
940
+
771
941
  const content: Record<string, string> = {};
772
942
  let currentSection = '';
773
943
  let currentText: string[] = [];
774
944
 
775
- for (const line of lines) {
776
- if (
777
- line.startsWith('EN:') ||
778
- line.startsWith('PT-M:') ||
779
- line.startsWith('PT-F:')
780
- ) {
781
- this.log(
782
- 'debug',
783
- `Skipping language header in section ${sectionIndex + 1}`,
784
- { line }
785
- );
786
- continue;
787
- }
788
-
945
+ for (const line of lines.slice(1)) {
789
946
  if (line.startsWith('#')) {
790
947
  if (currentSection && currentText.length > 0) {
791
948
  const key = currentSection
@@ -798,7 +955,10 @@ export class ArchetypeService {
798
955
  `Extracted section ${currentSection} in section ${
799
956
  sectionIndex + 1
800
957
  }`,
801
- { key, text: content[key] }
958
+ {
959
+ key,
960
+ text: content[key],
961
+ }
802
962
  );
803
963
  }
804
964
  currentSection = line.replace('# ', '');
@@ -825,7 +985,10 @@ export class ArchetypeService {
825
985
  `Extracted final section ${currentSection} in section ${
826
986
  sectionIndex + 1
827
987
  }`,
828
- { key, text: content[key] }
988
+ {
989
+ key,
990
+ text: content[key],
991
+ }
829
992
  );
830
993
  }
831
994
 
@@ -845,15 +1008,21 @@ export class ArchetypeService {
845
1008
 
846
1009
  if (Object.values(parsedContent).some((value) => !value)) {
847
1010
  this.log(
848
- 'warn',
1011
+ 'error',
849
1012
  `Malformed content response for section ${sectionIndex + 1}`,
850
1013
  {
851
1014
  parsedContent,
852
1015
  response: section,
853
1016
  }
854
1017
  );
1018
+ throw new Error(
1019
+ `Malformed content response for section ${sectionIndex + 1}`
1020
+ );
855
1021
  }
856
1022
 
1023
+ this.log('debug', `Parsed content for section ${sectionIndex + 1}`, {
1024
+ parsedContent,
1025
+ });
857
1026
  return parsedContent;
858
1027
  });
859
1028
 
@@ -863,26 +1032,21 @@ export class ArchetypeService {
863
1032
 
864
1033
  async generateLeonardoPrompts(
865
1034
  combinationString: string,
866
- gender: Gender, // User's gender, used for filtering results in ArchetypeWorkflow.execute
867
- language: string, // User's language, used for filtering results in ArchetypeWorkflow.execute
868
- descriptions: Array<{ descriptionEN: string }>
1035
+ gender: Gender, // Used for compatibility, but processes both genders
1036
+ language: string,
1037
+ descriptions: Array<{ descriptionEN: string }>,
1038
+ override: boolean = false
869
1039
  ) {
870
1040
  await this.log('info', 'Starting generateLeonardoPrompts', {
871
1041
  combinationString,
872
1042
  gender,
873
1043
  language,
1044
+ override,
874
1045
  });
875
1046
 
876
1047
  const db = this.context.drizzle();
877
-
878
- type Binary = 'male' | 'female';
879
-
880
- // Fetch archetypes for male and female with language = 'en-us'
881
- const allGenders: Binary[] = ['male', 'female'];
882
- const archetypesByGender: Record<Binary, any[]> = {
883
- male: [],
884
- female: [],
885
- };
1048
+ const allGenders: Gender[] = ['male', 'female'];
1049
+ const archetypesByGender: Record<Gender, any[]> = { male: [], female: [] };
886
1050
 
887
1051
  for (const g of allGenders) {
888
1052
  const archetypes = await db
@@ -892,41 +1056,40 @@ export class ArchetypeService {
892
1056
  and(
893
1057
  eq(schema.archetypesData.combination, combinationString),
894
1058
  eq(schema.archetypesData.gender, g),
895
- eq(schema.archetypesData.language, 'en-us')
1059
+ eq(schema.archetypesData.language, language)
896
1060
  )
897
1061
  )
1062
+ .orderBy(schema.archetypesData.archetypeIndex)
898
1063
  .execute();
899
1064
  archetypesByGender[g] = archetypes;
900
1065
  }
901
1066
 
902
- // Validate that we have archetypes for all genders (3 archetypes per gender)
903
- for (const g of allGenders) {
904
- if (archetypesByGender[g].length !== 3) {
905
- await this.log(
906
- 'error',
907
- `Expected 3 archetypes for gender ${g}, but found ${archetypesByGender[g].length}`,
908
- {
909
- combinationString,
910
- gender: g,
911
- }
912
- );
913
- throw new Error(
914
- `Expected 3 archetypes for gender ${g}, but found ${archetypesByGender[g].length}`
915
- );
916
- }
1067
+ const needsGeneration = allGenders.some(
1068
+ (g) =>
1069
+ archetypesByGender[g].length !== 3 ||
1070
+ archetypesByGender[g].some((a) => !a.leonardoPrompt)
1071
+ );
1072
+
1073
+ if (!override && !needsGeneration) {
1074
+ await this.log('info', 'Skipping Leonardo prompts generation', {
1075
+ reason: 'Leonardo prompts already exist for all archetypes',
1076
+ });
1077
+ return;
917
1078
  }
918
1079
 
919
1080
  await this.log('info', 'Fetched archetypes for Leonardo prompts', {
920
- maleArchetypesCount: archetypesByGender['male'].length,
921
- femaleArchetypesCount: archetypesByGender['female'].length,
922
- names: {
923
- male: archetypesByGender['male'].map((a) => a.name),
924
- female: archetypesByGender['female'].map((a) => a.name),
925
- },
1081
+ maleArchetypes: archetypesByGender['male'].map((a) => ({
1082
+ id: a.id,
1083
+ name: a.name,
1084
+ index: a.archetypeIndex,
1085
+ })),
1086
+ femaleArchetypes: archetypesByGender['female'].map((a) => ({
1087
+ id: a.id,
1088
+ name: a.name,
1089
+ index: a.archetypeIndex,
1090
+ })),
926
1091
  });
927
1092
 
928
- // Since descriptions are provided as an input (from generateDescriptionsAndVirtues),
929
- // we can use the same descriptions for all genders, as they are gender-neutral (en-us)
930
1093
  const promptMessages = this.context
931
1094
  .buildLLMMessages()
932
1095
  .generateCosmicMirrorArchetypeLeonardoPrompts(
@@ -957,115 +1120,73 @@ export class ArchetypeService {
957
1120
  const parsedPrompts = this.parseLeonardoPromptResponse(promptResponse);
958
1121
  await this.log('info', 'Parsed Leonardo prompts', { parsedPrompts });
959
1122
 
960
- // Update archetypes for all genders
961
1123
  for (const g of allGenders) {
962
1124
  for (let i = 0; i < 3; i++) {
1125
+ const archetype = archetypesByGender[g][i];
963
1126
  const index = (i + 1).toString();
964
- const leonardoPrompt =
965
- g === 'male'
966
- ? parsedPrompts[i].malePrompt
967
- : parsedPrompts[i].femalePrompt;
968
-
969
- // Update English entry (en-us)
970
- const enId = `${combinationString}:${g}:${index}`;
971
- await this.log(
972
- 'debug',
973
- `Updating Leonardo prompt for ${enId} (en-us)`,
974
- {
975
- leonardoPrompt,
976
- }
977
- );
978
-
979
- const enUpdateResult = await db
980
- .update(schema.archetypesData)
981
- .set({
982
- leonardoPrompt,
983
- updatedAt: new Date().getTime(),
984
- })
985
- .where(
986
- and(
987
- eq(schema.archetypesData.id, enId),
988
- eq(schema.archetypesData.language, 'en-us'),
989
- eq(schema.archetypesData.gender, g)
990
- )
991
- )
992
- .execute();
993
-
994
- await this.log('info', `Updated Leonardo prompt for ${enId} (en-us)`, {
995
- rowsAffected: enUpdateResult.rowsAffected,
996
- });
997
-
998
- if (enUpdateResult.rowsAffected === 0) {
1127
+ if (!archetype || archetype.archetypeIndex !== index) {
999
1128
  await this.log(
1000
1129
  'error',
1001
- `Failed to update Leonardo prompt for ${enId} (en-us)`,
1130
+ `Archetype index mismatch for gender ${g} at position ${i + 1}`,
1002
1131
  {
1003
- enId,
1004
- language: 'en-us',
1005
- gender: g,
1132
+ expectedIndex: index,
1133
+ actualIndex: archetype?.archetypeIndex,
1134
+ archetypeId: archetype?.id,
1006
1135
  }
1007
1136
  );
1008
1137
  throw new Error(
1009
- `Failed to update Leonardo prompt for ${enId} (en-us)`
1138
+ `Archetype index mismatch for gender ${g}: expected ${index}, got ${archetype?.archetypeIndex}`
1010
1139
  );
1011
1140
  }
1012
1141
 
1013
- // Update Portuguese entries (pt-br) for male and female
1014
- const ptId = `${combinationString}:${g}:${index}:pt`;
1015
- const ptGenders = ['male', 'female'];
1016
-
1017
- for (const ptGender of ptGenders) {
1018
- const ptLeonardoPrompt =
1019
- ptGender === 'male'
1020
- ? parsedPrompts[i].malePrompt
1021
- : parsedPrompts[i].femalePrompt;
1142
+ const leonardoPrompt =
1143
+ g === 'male'
1144
+ ? parsedPrompts[i].malePrompt
1145
+ : parsedPrompts[i].femalePrompt;
1146
+ const id = `${combinationString}:${g}:${index}${
1147
+ language === 'pt-br' ? ':pt' : ''
1148
+ }`;
1022
1149
 
1150
+ if (!override && archetype.leonardoPrompt) {
1023
1151
  await this.log(
1024
1152
  'debug',
1025
- `Updating Leonardo prompt for ${ptId} (pt-br, gender: ${ptGender})`,
1153
+ `Skipping existing Leonardo prompt for ${id}`,
1026
1154
  {
1027
- leonardoPrompt: ptLeonardoPrompt,
1155
+ leonardoPrompt: archetype.leonardoPrompt,
1156
+ archetypeIndex: index,
1157
+ gender: g,
1158
+ language,
1028
1159
  }
1029
1160
  );
1161
+ continue;
1162
+ }
1030
1163
 
1031
- const ptUpdateResult = await db
1032
- .update(schema.archetypesData)
1033
- .set({
1034
- leonardoPrompt: ptLeonardoPrompt,
1035
- updatedAt: new Date().getTime(),
1036
- })
1037
- .where(
1038
- and(
1039
- eq(schema.archetypesData.id, ptId),
1040
- eq(schema.archetypesData.language, 'pt-br'),
1041
- eq(schema.archetypesData.gender, ptGender)
1042
- )
1043
- )
1044
- .execute();
1164
+ await this.log('debug', `Updating Leonardo prompt for ${id}`, {
1165
+ leonardoPrompt,
1166
+ archetypeIndex: index,
1167
+ gender: g,
1168
+ language,
1169
+ });
1045
1170
 
1046
- await this.log(
1047
- 'info',
1048
- `Updated Leonardo prompt for ${ptId} (pt-br, gender: ${ptGender})`,
1049
- {
1050
- rowsAffected: ptUpdateResult.rowsAffected,
1051
- }
1052
- );
1171
+ const updateResult = await db
1172
+ .update(schema.archetypesData)
1173
+ .set({
1174
+ leonardoPrompt,
1175
+ updatedAt: new Date().getTime(),
1176
+ })
1177
+ .where(
1178
+ and(
1179
+ eq(schema.archetypesData.id, id),
1180
+ eq(schema.archetypesData.language, language),
1181
+ eq(schema.archetypesData.gender, g),
1182
+ eq(schema.archetypesData.archetypeIndex, index)
1183
+ )
1184
+ )
1185
+ .execute();
1053
1186
 
1054
- if (ptUpdateResult.rowsAffected === 0) {
1055
- await this.log(
1056
- 'error',
1057
- `Failed to update Leonardo prompt for ${ptId} (pt-br, gender: ${ptGender})`,
1058
- {
1059
- ptId,
1060
- language: 'pt-br',
1061
- gender: ptGender,
1062
- }
1063
- );
1064
- throw new Error(
1065
- `Failed to update Leonardo prompt for ${ptId} (pt-br, gender: ${ptGender})`
1066
- );
1067
- }
1068
- }
1187
+ await this.log('info', `Updated Leonardo prompt for ${id}`, {
1188
+ rowsAffected: updateResult.rowsAffected,
1189
+ });
1069
1190
  }
1070
1191
  }
1071
1192
 
@@ -1088,39 +1209,45 @@ export class ArchetypeService {
1088
1209
  lines,
1089
1210
  });
1090
1211
 
1091
- const prompts: LeonardoPrompt[] = [];
1092
- let currentArchetype = 0;
1212
+ if (lines.length !== 12) {
1213
+ this.log('error', 'Expected exactly 12 lines for 3 archetype prompts', {
1214
+ linesCount: lines.length,
1215
+ response,
1216
+ });
1217
+ throw new Error(
1218
+ `Expected 12 lines for 3 archetype prompts, got ${lines.length}`
1219
+ );
1220
+ }
1093
1221
 
1222
+ const prompts: LeonardoPrompt[] = [];
1094
1223
  for (let i = 0; i < lines.length; i += 4) {
1095
- // Expect pairs of label and prompt: "1.m", male prompt, "1.f", female prompt
1096
- const maleLabel = lines[i]; // "1.m"
1097
- const malePromptLine = lines[i + 1]; // Male prompt text
1098
- const femaleLabel = lines[i + 2]; // "1.f"
1099
- const femalePromptLine = lines[i + 3]; // Female prompt text
1224
+ const archetypeIndex = i / 4 + 1;
1225
+ const maleLabel = lines[i];
1226
+ const malePromptLine = lines[i + 1];
1227
+ const femaleLabel = lines[i + 2];
1228
+ const femalePromptLine = lines[i + 3];
1100
1229
 
1101
- currentArchetype++;
1102
- const expectedMaleLabel = `${currentArchetype}.m`;
1103
- const expectedFemaleLabel = `${currentArchetype}.f`;
1230
+ const expectedMaleLabel = `${archetypeIndex}.m`;
1231
+ const expectedFemaleLabel = `${archetypeIndex}.f`;
1104
1232
 
1105
1233
  if (
1106
1234
  !maleLabel?.startsWith(expectedMaleLabel) ||
1107
1235
  !femaleLabel?.startsWith(expectedFemaleLabel)
1108
1236
  ) {
1109
1237
  this.log(
1110
- 'warn',
1111
- `Malformed Leonardo prompt format for archetype ${currentArchetype}`,
1238
+ 'error',
1239
+ `Malformed Leonardo prompt format for archetype ${archetypeIndex}`,
1112
1240
  {
1113
1241
  maleLabel,
1114
1242
  malePromptLine,
1115
1243
  femaleLabel,
1116
1244
  femalePromptLine,
1117
- lines,
1245
+ expectedMaleLabel,
1246
+ expectedFemaleLabel,
1118
1247
  }
1119
1248
  );
1120
1249
  throw new Error(
1121
- `Expected ${expectedMaleLabel} and ${expectedFemaleLabel} at lines ${i} and ${
1122
- i + 2
1123
- }`
1250
+ `Malformed prompt format for archetype ${archetypeIndex}`
1124
1251
  );
1125
1252
  }
1126
1253
 
@@ -1129,42 +1256,30 @@ export class ArchetypeService {
1129
1256
 
1130
1257
  if (!malePrompt || !femalePrompt) {
1131
1258
  this.log(
1132
- 'warn',
1133
- `Empty Leonardo prompt for archetype ${currentArchetype}`,
1259
+ 'error',
1260
+ `Empty Leonardo prompt for archetype ${archetypeIndex}`,
1134
1261
  {
1135
1262
  malePrompt,
1136
1263
  femalePrompt,
1137
- malePromptLine,
1138
- femalePromptLine,
1139
1264
  }
1140
1265
  );
1141
- throw new Error(`Empty prompt for archetype ${currentArchetype}`);
1266
+ throw new Error(`Empty prompt for archetype ${archetypeIndex}`);
1142
1267
  }
1143
1268
 
1144
1269
  prompts.push({ malePrompt, femalePrompt });
1145
- this.log('debug', `Parsed prompts for archetype ${currentArchetype}`, {
1270
+ this.log('debug', `Parsed prompts for archetypeIndex ${archetypeIndex}`, {
1146
1271
  malePrompt,
1147
1272
  femalePrompt,
1148
1273
  });
1149
1274
  }
1150
1275
 
1151
- if (prompts.length !== 3) {
1152
- this.log('error', 'Expected exactly 3 archetype prompts', {
1153
- promptsCount: prompts.length,
1154
- response,
1155
- });
1156
- throw new Error(
1157
- `Expected exactly 3 archetype prompts, but got ${prompts.length}`
1158
- );
1159
- }
1160
-
1161
1276
  this.log('info', 'Completed parseLeonardoPromptResponse', { prompts });
1162
1277
  return prompts;
1163
1278
  }
1164
1279
 
1165
1280
  async fetchArchetypesFromDB(
1166
1281
  combinationString: string,
1167
- gender: Gender,
1282
+ gender: Gender | null, // null to fetch both genders
1168
1283
  language: string
1169
1284
  ) {
1170
1285
  await this.log('debug', 'Executing fetchArchetypesFromDB', {
@@ -1173,20 +1288,23 @@ export class ArchetypeService {
1173
1288
  language,
1174
1289
  });
1175
1290
  const db = this.context.drizzle();
1291
+ const conditions = [
1292
+ eq(schema.archetypesData.combination, combinationString),
1293
+ eq(schema.archetypesData.language, language),
1294
+ ];
1295
+ if (gender) {
1296
+ conditions.push(eq(schema.archetypesData.gender, gender));
1297
+ }
1176
1298
  const result = await db
1177
1299
  .select()
1178
1300
  .from(schema.archetypesData)
1179
- .where(
1180
- and(
1181
- eq(schema.archetypesData.combination, combinationString),
1182
- eq(schema.archetypesData.gender, gender),
1183
- eq(schema.archetypesData.language, language)
1184
- )
1185
- )
1301
+ .where(and(...conditions))
1302
+ .orderBy(schema.archetypesData.archetypeIndex)
1186
1303
  .execute();
1187
1304
  await this.log('debug', 'fetchArchetypesFromDB result', {
1188
1305
  resultCount: result.length,
1189
- result,
1306
+ indexes: result.map((r) => r.archetypeIndex),
1307
+ genders: result.map((r) => r.gender),
1190
1308
  });
1191
1309
  return result;
1192
1310
  }