ei-tui 0.1.25 → 0.3.1
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/README.md +42 -0
- package/package.json +2 -1
- package/src/README.md +4 -11
- package/src/cli/README.md +87 -7
- package/src/cli/commands/facts.ts +2 -2
- package/src/cli/commands/people.ts +2 -2
- package/src/cli/commands/quotes.ts +2 -2
- package/src/cli/commands/topics.ts +2 -2
- package/src/cli/mcp.ts +94 -0
- package/src/cli/retrieval.ts +67 -31
- package/src/cli.ts +64 -23
- package/src/core/AGENTS.md +1 -1
- package/src/core/constants/built-in-facts.ts +49 -0
- package/src/core/constants/index.ts +1 -0
- package/src/core/context-utils.ts +0 -1
- package/src/core/embedding-service.ts +8 -0
- package/src/core/handlers/dedup.ts +11 -23
- package/src/core/handlers/heartbeat.ts +2 -3
- package/src/core/handlers/human-extraction.ts +96 -30
- package/src/core/handlers/human-matching.ts +328 -248
- package/src/core/handlers/index.ts +8 -6
- package/src/core/handlers/persona-generation.ts +8 -8
- package/src/core/handlers/rewrite.ts +4 -51
- package/src/core/handlers/utils.ts +23 -1
- package/src/core/heartbeat-manager.ts +2 -4
- package/src/core/human-data-manager.ts +38 -36
- package/src/core/message-manager.ts +10 -10
- package/src/core/orchestrators/ceremony.ts +49 -44
- package/src/core/orchestrators/dedup-phase.ts +2 -4
- package/src/core/orchestrators/human-extraction.ts +351 -207
- package/src/core/orchestrators/index.ts +6 -4
- package/src/core/orchestrators/persona-generation.ts +3 -3
- package/src/core/processor.ts +167 -20
- package/src/core/prompt-context-builder.ts +4 -6
- package/src/core/state/human.ts +1 -26
- package/src/core/state/personas.ts +2 -2
- package/src/core/state-manager.ts +107 -14
- package/src/core/tools/builtin/read-memory.ts +13 -18
- package/src/core/types/data-items.ts +3 -4
- package/src/core/types/entities.ts +7 -4
- package/src/core/types/enums.ts +6 -9
- package/src/core/types/llm.ts +2 -2
- package/src/core/utils/crossFind.ts +2 -5
- package/src/core/utils/event-windows.ts +31 -0
- package/src/integrations/claude-code/importer.ts +14 -5
- package/src/integrations/claude-code/types.ts +3 -0
- package/src/integrations/cursor/importer.ts +282 -0
- package/src/integrations/cursor/index.ts +10 -0
- package/src/integrations/cursor/reader.ts +209 -0
- package/src/integrations/cursor/types.ts +140 -0
- package/src/integrations/opencode/importer.ts +14 -4
- package/src/prompts/AGENTS.md +73 -1
- package/src/prompts/ceremony/dedup.ts +0 -33
- package/src/prompts/ceremony/rewrite.ts +6 -41
- package/src/prompts/ceremony/types.ts +4 -4
- package/src/prompts/generation/descriptions.ts +2 -2
- package/src/prompts/generation/types.ts +2 -2
- package/src/prompts/heartbeat/types.ts +2 -2
- package/src/prompts/human/event-scan.ts +122 -0
- package/src/prompts/human/fact-find.ts +106 -0
- package/src/prompts/human/fact-scan.ts +0 -2
- package/src/prompts/human/index.ts +17 -10
- package/src/prompts/human/person-match.ts +65 -0
- package/src/prompts/human/person-scan.ts +52 -59
- package/src/prompts/human/person-update.ts +241 -0
- package/src/prompts/human/topic-match.ts +65 -0
- package/src/prompts/human/topic-scan.ts +51 -71
- package/src/prompts/human/topic-update.ts +295 -0
- package/src/prompts/human/types.ts +63 -40
- package/src/prompts/index.ts +4 -8
- package/src/prompts/persona/topics-update.ts +2 -2
- package/src/prompts/persona/traits.ts +2 -2
- package/src/prompts/persona/types.ts +3 -3
- package/src/prompts/response/index.ts +1 -1
- package/src/prompts/response/sections.ts +9 -12
- package/src/prompts/response/types.ts +2 -3
- package/src/storage/embeddings.ts +1 -1
- package/src/storage/index.ts +1 -0
- package/src/storage/indexed.ts +174 -0
- package/src/storage/merge.ts +67 -2
- package/tui/src/commands/me.tsx +5 -14
- package/tui/src/commands/settings.tsx +15 -0
- package/tui/src/context/ei.tsx +5 -14
- package/tui/src/util/yaml-serializers.ts +76 -33
- package/src/cli/commands/traits.ts +0 -25
- package/src/prompts/human/item-match.ts +0 -74
- package/src/prompts/human/item-update.ts +0 -364
- package/src/prompts/human/trait-scan.ts +0 -115
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
OpenCodeSettings,
|
|
8
8
|
BackupConfig,
|
|
9
9
|
Fact,
|
|
10
|
-
|
|
10
|
+
PersonaTrait,
|
|
11
11
|
Topic,
|
|
12
12
|
Person,
|
|
13
13
|
PersonaTopic,
|
|
@@ -23,15 +23,13 @@ import type {
|
|
|
23
23
|
} from "../../../src/core/types.js";
|
|
24
24
|
import { ContextStatus } from "../../../src/core/types.js";
|
|
25
25
|
import type { ClaudeCodeSettings } from "../../../src/integrations/claude-code/types.js";
|
|
26
|
+
import type { CursorSettings } from "../../../src/integrations/cursor/types.js";
|
|
27
|
+
import { BUILT_IN_FACT_NAMES } from "../../../src/core/constants/built-in-facts.js";
|
|
26
28
|
|
|
27
29
|
// =============================================================================
|
|
28
30
|
// TYPES FOR YAML EDITING
|
|
29
31
|
// =============================================================================
|
|
30
32
|
|
|
31
|
-
interface EditableTrait extends Trait {
|
|
32
|
-
_delete?: boolean;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
33
|
interface EditableTopic extends Topic {
|
|
36
34
|
_delete?: boolean;
|
|
37
35
|
}
|
|
@@ -64,7 +62,6 @@ interface EditablePersonaData {
|
|
|
64
62
|
|
|
65
63
|
interface EditableHumanData {
|
|
66
64
|
facts: EditableFact[];
|
|
67
|
-
traits: EditableTrait[];
|
|
68
65
|
topics: EditableTopic[];
|
|
69
66
|
people: EditablePerson[];
|
|
70
67
|
}
|
|
@@ -190,7 +187,7 @@ export function newPersonaFromYAML(yamlContent: string, allTools?: ToolDefinitio
|
|
|
190
187
|
t.name === PLACEHOLDER_TRAIT.name &&
|
|
191
188
|
t.description === PLACEHOLDER_TRAIT.description;
|
|
192
189
|
|
|
193
|
-
const traits:
|
|
190
|
+
const traits: PersonaTrait[] = [];
|
|
194
191
|
for (const t of data.traits ?? []) {
|
|
195
192
|
if (isTraitPlaceholder(t)) {
|
|
196
193
|
continue;
|
|
@@ -313,7 +310,7 @@ export function personaFromYAML(yamlContent: string, original: PersonaEntity, al
|
|
|
313
310
|
t.name === PLACEHOLDER_TRAIT.name &&
|
|
314
311
|
t.description === PLACEHOLDER_TRAIT.description;
|
|
315
312
|
|
|
316
|
-
const traits:
|
|
313
|
+
const traits: PersonaTrait[] = [];
|
|
317
314
|
for (const t of data.traits ?? []) {
|
|
318
315
|
if (isTraitPlaceholder(t)) {
|
|
319
316
|
continue;
|
|
@@ -406,7 +403,6 @@ export function personaFromYAML(yamlContent: string, original: PersonaEntity, al
|
|
|
406
403
|
export function humanToYAML(human: HumanEntity, personaLookup?: Map<string, string>): string {
|
|
407
404
|
const data: EditableHumanData = {
|
|
408
405
|
facts: human.facts.map(f => ({ ...f, _delete: false })),
|
|
409
|
-
traits: human.traits.map(t => ({ ...t, _delete: false })),
|
|
410
406
|
topics: human.topics.map(t => ({ ...t, _delete: false })),
|
|
411
407
|
people: human.people.map(p => ({ ...p, _delete: false })),
|
|
412
408
|
};
|
|
@@ -414,27 +410,28 @@ export function humanToYAML(human: HumanEntity, personaLookup?: Map<string, stri
|
|
|
414
410
|
return YAML.stringify(data, {
|
|
415
411
|
lineWidth: 0,
|
|
416
412
|
})
|
|
417
|
-
.replace(/^(\s+validated:\s+\S+)$/mg, '$1 # none | ei | human')
|
|
418
413
|
.replace(/^(\s+)(learned_by: )(.+)$/mg, (_, indent, key, val) => {
|
|
419
414
|
const trimmed = val.trim();
|
|
420
415
|
const displayName = personaLookup?.get(trimmed) ?? trimmed;
|
|
421
416
|
return `${indent}# [read-only] ${key}${displayName}`;
|
|
422
417
|
})
|
|
423
|
-
.replace(/^(\s+)(last_changed_by: .+)$/mg,
|
|
418
|
+
.replace(/^(\s+)(last_changed_by: )(.+)$/mg, (_, indent, key, val) => {
|
|
419
|
+
const trimmed = val.trim();
|
|
420
|
+
const displayName = personaLookup?.get(trimmed) ?? trimmed;
|
|
421
|
+
return `${indent}# [read-only] ${key}${displayName}`;
|
|
422
|
+
});
|
|
424
423
|
}
|
|
425
424
|
|
|
426
425
|
export interface HumanYAMLResult {
|
|
427
426
|
facts: Fact[];
|
|
428
|
-
traits: Trait[];
|
|
429
427
|
topics: Topic[];
|
|
430
428
|
people: Person[];
|
|
431
429
|
deletedFactIds: string[];
|
|
432
|
-
deletedTraitIds: string[];
|
|
433
430
|
deletedTopicIds: string[];
|
|
434
431
|
deletedPersonIds: string[];
|
|
435
432
|
}
|
|
436
433
|
|
|
437
|
-
export function humanFromYAML(yamlContent: string): HumanYAMLResult {
|
|
434
|
+
export function humanFromYAML(yamlContent: string, original?: HumanEntity): HumanYAMLResult {
|
|
438
435
|
// Strip read-only comment lines before parsing so users can't accidentally corrupt them
|
|
439
436
|
const stripped = yamlContent
|
|
440
437
|
.split('\n')
|
|
@@ -443,36 +440,32 @@ export function humanFromYAML(yamlContent: string): HumanYAMLResult {
|
|
|
443
440
|
const data = YAML.parse(stripped) as EditableHumanData;
|
|
444
441
|
|
|
445
442
|
const deletedFactIds: string[] = [];
|
|
446
|
-
const deletedTraitIds: string[] = [];
|
|
447
443
|
const deletedTopicIds: string[] = [];
|
|
448
444
|
const deletedPersonIds: string[] = [];
|
|
449
445
|
|
|
450
446
|
const facts: Fact[] = [];
|
|
451
447
|
for (const f of data.facts ?? []) {
|
|
452
|
-
if (f._delete) {
|
|
448
|
+
if (f._delete && !BUILT_IN_FACT_NAMES.has(f.name)) {
|
|
453
449
|
deletedFactIds.push(f.id);
|
|
454
450
|
} else {
|
|
455
|
-
const { _delete, ...
|
|
451
|
+
const { _delete, ...parsed } = f;
|
|
452
|
+
const originalFact = original?.facts.find(of => of.id === parsed.id);
|
|
453
|
+
const fact = originalFact ? { ...originalFact, ...parsed } : parsed;
|
|
454
|
+
if (fact.description && !fact.validated_date) {
|
|
455
|
+
fact.validated_date = new Date().toISOString();
|
|
456
|
+
}
|
|
456
457
|
facts.push(fact);
|
|
457
458
|
}
|
|
458
459
|
}
|
|
459
460
|
|
|
460
|
-
const traits: Trait[] = [];
|
|
461
|
-
for (const t of data.traits ?? []) {
|
|
462
|
-
if (t._delete) {
|
|
463
|
-
deletedTraitIds.push(t.id);
|
|
464
|
-
} else {
|
|
465
|
-
const { _delete, ...trait } = t;
|
|
466
|
-
traits.push(trait);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
461
|
const topics: Topic[] = [];
|
|
471
462
|
for (const t of data.topics ?? []) {
|
|
472
463
|
if (t._delete) {
|
|
473
464
|
deletedTopicIds.push(t.id);
|
|
474
465
|
} else {
|
|
475
|
-
const { _delete, ...
|
|
466
|
+
const { _delete, ...parsed } = t;
|
|
467
|
+
const originalTopic = original?.topics.find(ot => ot.id === parsed.id);
|
|
468
|
+
const topic = originalTopic ? { ...originalTopic, ...parsed } : parsed;
|
|
476
469
|
topics.push(topic);
|
|
477
470
|
}
|
|
478
471
|
}
|
|
@@ -482,18 +475,18 @@ export function humanFromYAML(yamlContent: string): HumanYAMLResult {
|
|
|
482
475
|
if (p._delete) {
|
|
483
476
|
deletedPersonIds.push(p.id);
|
|
484
477
|
} else {
|
|
485
|
-
const { _delete, ...
|
|
478
|
+
const { _delete, ...parsed } = p;
|
|
479
|
+
const originalPerson = original?.people.find(op => op.id === parsed.id);
|
|
480
|
+
const person = originalPerson ? { ...originalPerson, ...parsed } : parsed;
|
|
486
481
|
people.push(person);
|
|
487
482
|
}
|
|
488
483
|
}
|
|
489
484
|
|
|
490
485
|
return {
|
|
491
486
|
facts,
|
|
492
|
-
traits,
|
|
493
487
|
topics,
|
|
494
488
|
people,
|
|
495
489
|
deletedFactIds,
|
|
496
|
-
deletedTraitIds,
|
|
497
490
|
deletedTopicIds,
|
|
498
491
|
deletedPersonIds,
|
|
499
492
|
};
|
|
@@ -520,11 +513,22 @@ interface EditableSettingsData {
|
|
|
520
513
|
polling_interval_ms?: number | null;
|
|
521
514
|
last_sync?: string | null;
|
|
522
515
|
extraction_point?: string | null;
|
|
516
|
+
extraction_model?: string | null;
|
|
517
|
+
extraction_token_limit?: string | number | null;
|
|
523
518
|
};
|
|
524
519
|
claudeCode?: {
|
|
525
520
|
integration?: boolean | null;
|
|
526
521
|
polling_interval_ms?: number | null;
|
|
527
522
|
last_sync?: string | null;
|
|
523
|
+
extraction_point?: string | null;
|
|
524
|
+
extraction_model?: string | null;
|
|
525
|
+
extraction_token_limit?: string | number | null;
|
|
526
|
+
};
|
|
527
|
+
cursor?: {
|
|
528
|
+
integration?: boolean | null;
|
|
529
|
+
polling_interval_ms?: number | null;
|
|
530
|
+
last_sync?: string | null;
|
|
531
|
+
extraction_point?: string | null;
|
|
528
532
|
};
|
|
529
533
|
backup?: {
|
|
530
534
|
enabled?: boolean | null;
|
|
@@ -550,13 +554,24 @@ export function settingsToYAML(settings: HumanSettings | undefined): string {
|
|
|
550
554
|
opencode: {
|
|
551
555
|
integration: settings?.opencode?.integration ?? false,
|
|
552
556
|
polling_interval_ms: settings?.opencode?.polling_interval_ms ?? 1800000,
|
|
557
|
+
extraction_model: settings?.opencode?.extraction_model ?? 'default',
|
|
558
|
+
extraction_token_limit: settings?.opencode?.extraction_token_limit ?? 'default',
|
|
553
559
|
last_sync: settings?.opencode?.last_sync ?? null,
|
|
554
560
|
extraction_point: settings?.opencode?.extraction_point ?? null,
|
|
555
561
|
},
|
|
556
562
|
claudeCode: {
|
|
557
563
|
integration: settings?.claudeCode?.integration ?? false,
|
|
558
564
|
polling_interval_ms: settings?.claudeCode?.polling_interval_ms ?? 1800000,
|
|
565
|
+
extraction_model: settings?.claudeCode?.extraction_model ?? 'default',
|
|
566
|
+
extraction_token_limit: settings?.claudeCode?.extraction_token_limit ?? 'default',
|
|
559
567
|
last_sync: settings?.claudeCode?.last_sync ?? null,
|
|
568
|
+
extraction_point: settings?.claudeCode?.extraction_point ?? null,
|
|
569
|
+
},
|
|
570
|
+
cursor: {
|
|
571
|
+
integration: settings?.cursor?.integration ?? false,
|
|
572
|
+
polling_interval_ms: settings?.cursor?.polling_interval_ms ?? 1800000,
|
|
573
|
+
last_sync: settings?.cursor?.last_sync ?? null,
|
|
574
|
+
extraction_point: settings?.cursor?.extraction_point ?? null,
|
|
560
575
|
},
|
|
561
576
|
backup: {
|
|
562
577
|
enabled: settings?.backup?.enabled ?? false,
|
|
@@ -569,7 +584,9 @@ export function settingsToYAML(settings: HumanSettings | undefined): string {
|
|
|
569
584
|
lineWidth: 0,
|
|
570
585
|
})
|
|
571
586
|
.replace(/^(\s+)(last_sync: .+)$/mg, '$1# [read-only] $2')
|
|
572
|
-
.replace(/^(\s+)(extraction_point: .+)$/mg, '$1# [read-only] $2')
|
|
587
|
+
.replace(/^(\s+)(extraction_point: .+)$/mg, '$1# [read-only] $2')
|
|
588
|
+
.replace(/^(\s+)(extraction_model: .+)$/mg, '$1$2 # e.g. Anthropic:claude-haiku-4-5')
|
|
589
|
+
.replace(/^(\s+)(extraction_token_limit: .+)$/mg, '$1$2 # e.g. 100000 for Haiku');
|
|
573
590
|
}
|
|
574
591
|
export function settingsFromYAML(yamlContent: string, original: HumanSettings | undefined): HumanSettings {
|
|
575
592
|
const data = YAML.parse(yamlContent) as EditableSettingsData;
|
|
@@ -596,6 +613,12 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
|
|
|
596
613
|
last_sync: original?.opencode?.last_sync,
|
|
597
614
|
extraction_point: original?.opencode?.extraction_point,
|
|
598
615
|
processed_sessions: original?.opencode?.processed_sessions,
|
|
616
|
+
extraction_model: (data.opencode.extraction_model != null && data.opencode.extraction_model !== 'default')
|
|
617
|
+
? data.opencode.extraction_model
|
|
618
|
+
: undefined,
|
|
619
|
+
extraction_token_limit: (data.opencode.extraction_token_limit != null && data.opencode.extraction_token_limit !== 'default')
|
|
620
|
+
? Number(data.opencode.extraction_token_limit)
|
|
621
|
+
: undefined,
|
|
599
622
|
};
|
|
600
623
|
}
|
|
601
624
|
|
|
@@ -605,7 +628,25 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
|
|
|
605
628
|
integration: nullToUndefined(data.claudeCode.integration),
|
|
606
629
|
polling_interval_ms: nullToUndefined(data.claudeCode.polling_interval_ms),
|
|
607
630
|
last_sync: original?.claudeCode?.last_sync,
|
|
631
|
+
extraction_point: original?.claudeCode?.extraction_point,
|
|
608
632
|
processed_sessions: original?.claudeCode?.processed_sessions,
|
|
633
|
+
extraction_model: (data.claudeCode.extraction_model != null && data.claudeCode.extraction_model !== 'default')
|
|
634
|
+
? data.claudeCode.extraction_model
|
|
635
|
+
: undefined,
|
|
636
|
+
extraction_token_limit: (data.claudeCode.extraction_token_limit != null && data.claudeCode.extraction_token_limit !== 'default')
|
|
637
|
+
? Number(data.claudeCode.extraction_token_limit)
|
|
638
|
+
: undefined,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
let cursor: CursorSettings | undefined;
|
|
643
|
+
if (data.cursor) {
|
|
644
|
+
cursor = {
|
|
645
|
+
integration: nullToUndefined(data.cursor.integration),
|
|
646
|
+
polling_interval_ms: nullToUndefined(data.cursor.polling_interval_ms),
|
|
647
|
+
last_sync: original?.cursor?.last_sync,
|
|
648
|
+
extraction_point: original?.cursor?.extraction_point,
|
|
649
|
+
processed_sessions: original?.cursor?.processed_sessions,
|
|
609
650
|
};
|
|
610
651
|
}
|
|
611
652
|
|
|
@@ -629,6 +670,7 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
|
|
|
629
670
|
ceremony,
|
|
630
671
|
opencode,
|
|
631
672
|
claudeCode,
|
|
673
|
+
cursor,
|
|
632
674
|
backup,
|
|
633
675
|
};
|
|
634
676
|
}
|
|
@@ -896,7 +938,8 @@ export function contextFromYAML(yamlContent: string): ContextYAMLResult {
|
|
|
896
938
|
if (m._delete) {
|
|
897
939
|
deletedMessageIds.push(m.id);
|
|
898
940
|
} else {
|
|
899
|
-
|
|
941
|
+
const normalized = (m.context_status ?? 'default').toString().toLowerCase() as ContextStatus;
|
|
942
|
+
messages.push({ id: m.id, context_status: normalized });
|
|
900
943
|
}
|
|
901
944
|
}
|
|
902
945
|
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { loadLatestState, retrieve } from "../retrieval";
|
|
2
|
-
import type { TraitResult } from "../retrieval";
|
|
3
|
-
|
|
4
|
-
export async function execute(query: string, limit: number): Promise<TraitResult[]> {
|
|
5
|
-
const state = await loadLatestState();
|
|
6
|
-
if (!state) {
|
|
7
|
-
console.error("No saved state found. Is EI_DATA_PATH set correctly?");
|
|
8
|
-
return [];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const traits = state.human.traits;
|
|
12
|
-
if (traits.length === 0) {
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const results = await retrieve(traits, query, limit);
|
|
17
|
-
|
|
18
|
-
return results.map(trait => ({
|
|
19
|
-
id: trait.id,
|
|
20
|
-
name: trait.name,
|
|
21
|
-
description: trait.description,
|
|
22
|
-
strength: trait.strength ?? 0.5,
|
|
23
|
-
sentiment: trait.sentiment,
|
|
24
|
-
}));
|
|
25
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { ItemMatchPromptData, PromptOutput } from "./types.js";
|
|
2
|
-
|
|
3
|
-
export function buildHumanItemMatchPrompt(data: ItemMatchPromptData): PromptOutput {
|
|
4
|
-
if (!data.candidate_type || !data.candidate_name) {
|
|
5
|
-
throw new Error("buildHumanItemMatchPrompt: candidate_type and candidate_name are required");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const typeLabel = data.candidate_type.toUpperCase();
|
|
9
|
-
|
|
10
|
-
const system = `# Task
|
|
11
|
-
|
|
12
|
-
You are checking if a new ${typeLabel} already exists in our database.
|
|
13
|
-
|
|
14
|
-
We track four types of data about the human user:
|
|
15
|
-
- **FACT**: Biographical data (name, birthday, job, etc.)
|
|
16
|
-
- **TRAIT**: Personality patterns (introverted, analytical, etc.)
|
|
17
|
-
- **TOPIC**: Interests, goals, concerns, stories
|
|
18
|
-
- **PERSON**: People in their life (family, friends, coworkers)
|
|
19
|
-
|
|
20
|
-
Your job is to find if this candidate matches ANY existing entry — even if it's stored as a different type.
|
|
21
|
-
|
|
22
|
-
## Why Cross-Type Matching?
|
|
23
|
-
|
|
24
|
-
Sometimes the same concept gets detected as different types:
|
|
25
|
-
- "Juliet" might be detected as a TOPIC but should be a PERSON
|
|
26
|
-
- "Birthday" might be detected as a TOPIC but is actually a FACT
|
|
27
|
-
- "Always planning ahead" might be a TOPIC but is really a TRAIT
|
|
28
|
-
|
|
29
|
-
If you find a match in a DIFFERENT type, still return it! The system will handle the type mismatch.
|
|
30
|
-
|
|
31
|
-
## Matching Rules
|
|
32
|
-
|
|
33
|
-
1. **Exact match**: Same name/concept → return its ID
|
|
34
|
-
2. **Similar match**: Clearly the same thing with different wording → return its ID
|
|
35
|
-
3. **Cross-type match**: Same concept stored as different type → return its ID
|
|
36
|
-
4. **No match**: Genuinely new information → return "new"
|
|
37
|
-
|
|
38
|
-
# Existing Data
|
|
39
|
-
|
|
40
|
-
The following entries are already in our database. Same-type entries have full descriptions; cross-type entries are truncated for brevity.
|
|
41
|
-
|
|
42
|
-
\`\`\`json
|
|
43
|
-
${JSON.stringify(data.all_items, null, 2)}
|
|
44
|
-
\`\`\`
|
|
45
|
-
|
|
46
|
-
# Response Format
|
|
47
|
-
|
|
48
|
-
Return ONLY the ID of the matching entry, or "new" if no match exists.
|
|
49
|
-
|
|
50
|
-
\`\`\`json
|
|
51
|
-
{
|
|
52
|
-
"matched_guid": "uuid-of-matching-entry" | "new"
|
|
53
|
-
}
|
|
54
|
-
\`\`\`
|
|
55
|
-
|
|
56
|
-
**Return JSON only.**`;
|
|
57
|
-
|
|
58
|
-
const user = `# Candidate to Match
|
|
59
|
-
|
|
60
|
-
Type: ${typeLabel}
|
|
61
|
-
Name: ${data.candidate_name}
|
|
62
|
-
Value: ${data.candidate_value}
|
|
63
|
-
|
|
64
|
-
Find the best match in existing data, or return "new" if this is genuinely new.
|
|
65
|
-
|
|
66
|
-
**Return JSON:**
|
|
67
|
-
\`\`\`json
|
|
68
|
-
{
|
|
69
|
-
"matched_guid": "..." | "new"
|
|
70
|
-
}
|
|
71
|
-
\`\`\``;
|
|
72
|
-
|
|
73
|
-
return { system, user };
|
|
74
|
-
}
|