@devflow-tools/memory-engine 0.17.0 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/consolidator.d.ts.map +1 -1
  2. package/dist/consolidator.js +24 -15
  3. package/dist/consolidator.js.map +1 -1
  4. package/dist/durable-memory-text.d.ts +8 -0
  5. package/dist/durable-memory-text.d.ts.map +1 -0
  6. package/dist/durable-memory-text.js +35 -0
  7. package/dist/durable-memory-text.js.map +1 -0
  8. package/dist/event-grouper.d.ts +1 -0
  9. package/dist/event-grouper.d.ts.map +1 -1
  10. package/dist/event-grouper.js +18 -13
  11. package/dist/event-grouper.js.map +1 -1
  12. package/dist/explicit-memory-enricher.d.ts.map +1 -1
  13. package/dist/explicit-memory-enricher.js +2 -1
  14. package/dist/explicit-memory-enricher.js.map +1 -1
  15. package/dist/hybrid-search.d.ts +23 -0
  16. package/dist/hybrid-search.d.ts.map +1 -1
  17. package/dist/hybrid-search.js +171 -17
  18. package/dist/hybrid-search.js.map +1 -1
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +2 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/memory-capsule.d.ts +3 -0
  24. package/dist/memory-capsule.d.ts.map +1 -1
  25. package/dist/memory-capsule.js +27 -0
  26. package/dist/memory-capsule.js.map +1 -1
  27. package/dist/memory-engine.d.ts +1 -0
  28. package/dist/memory-engine.d.ts.map +1 -1
  29. package/dist/memory-engine.js +83 -37
  30. package/dist/memory-engine.js.map +1 -1
  31. package/dist/memory-gate.d.ts +6 -0
  32. package/dist/memory-gate.d.ts.map +1 -1
  33. package/dist/memory-gate.js +293 -30
  34. package/dist/memory-gate.js.map +1 -1
  35. package/dist/memory-maintenance.d.ts +3 -1
  36. package/dist/memory-maintenance.d.ts.map +1 -1
  37. package/dist/memory-maintenance.js +46 -1
  38. package/dist/memory-maintenance.js.map +1 -1
  39. package/dist/memory-relevance.d.ts +7 -1
  40. package/dist/memory-relevance.d.ts.map +1 -1
  41. package/dist/memory-relevance.js +54 -2
  42. package/dist/memory-relevance.js.map +1 -1
  43. package/dist/memory-store.d.ts +1 -0
  44. package/dist/memory-store.d.ts.map +1 -1
  45. package/dist/memory-store.js +57 -18
  46. package/dist/memory-store.js.map +1 -1
  47. package/dist/migrations/v11-to-v12.d.ts +15 -1
  48. package/dist/migrations/v11-to-v12.d.ts.map +1 -1
  49. package/dist/migrations/v11-to-v12.js +215 -15
  50. package/dist/migrations/v11-to-v12.js.map +1 -1
  51. package/dist/semantic-memory-compiler.d.ts +29 -0
  52. package/dist/semantic-memory-compiler.d.ts.map +1 -0
  53. package/dist/semantic-memory-compiler.js +149 -0
  54. package/dist/semantic-memory-compiler.js.map +1 -0
  55. package/dist/session-summarizer.d.ts +2 -0
  56. package/dist/session-summarizer.d.ts.map +1 -1
  57. package/dist/session-summarizer.js.map +1 -1
  58. package/package.json +4 -3
@@ -12,7 +12,8 @@ import { join } from 'node:path';
12
12
  import { isObservationType, observationDedupKey, } from './observation-types.js';
13
13
  import { evaluateObservationQuality } from './observation-quality.js';
14
14
  import { mergeSemanticEntities, normalizeEntityKey, normalizeSemanticMemory, } from './semantic-memory.js';
15
- import { validateMemoryCapsules } from './memory-capsule.js';
15
+ import { validateEvidenceSpans, validateMemoryCapsules } from './memory-capsule.js';
16
+ import { emitSemanticControlLog } from '@devflow-tools/telemetry';
16
17
  const BATCH_CONCEPT_STOP_WORDS = new Set([
17
18
  'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
18
19
  'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'should',
@@ -180,10 +181,12 @@ export class MemoryGate {
180
181
  if (this.engine && signals.length > 0) {
181
182
  try {
182
183
  this.engine.recordSignals(signals);
183
- console.log('[MemoryGate] Signals detected', {
184
- count: signals.length,
185
- types: signals.map(s => s.type),
186
- sessionId: event.sessionId,
184
+ emitSemanticControlLog({
185
+ event: 'memory.write.completed',
186
+ identity: { projectRoot: this.projectRoot, sessionId: event.sessionId },
187
+ level: 'debug',
188
+ timestamp: Date.now(),
189
+ data: { stage: 'signals', count: signals.length, types: signals.map(signal => signal.type) },
187
190
  });
188
191
  }
189
192
  catch { /* non-critical */ }
@@ -263,9 +266,9 @@ export class MemoryGate {
263
266
  .filter(event => sessionId === undefined || event.session_id === sessionId);
264
267
  if (events.length === 0)
265
268
  return null;
266
- const groups = groupEventsBySession(events);
269
+ const groups = groupEventsBySession(events, { projectRoot: this.projectRoot });
267
270
  const preserveSessionIds = this.getActiveSessionIds(groups);
268
- const { selected, discarded } = triageGroupsWithDisposition(groups, 8, { preserveSessionIds });
271
+ const { selected, discarded } = triageGroupsWithDisposition(groups, 8, { preserveSessionIds, projectRoot: this.projectRoot });
269
272
  const discardedEventIds = discarded.flatMap(group => group.events).map(event => event.id);
270
273
  if (discardedEventIds.length > 0) {
271
274
  const discardedSessionIds = [...new Set(discarded.flatMap(group => group.events).map(event => event.session_id).filter(Boolean))];
@@ -452,10 +455,12 @@ export class MemoryGate {
452
455
  leases.commitLease(batchId);
453
456
  });
454
457
  transaction();
455
- console.log('[MemoryGate] Distilled observations saved by batch', {
456
- batchId,
457
- observationCount: validObs.length,
458
- eventsMarked: lease.eventIds.length,
458
+ emitSemanticControlLog({
459
+ event: 'memory.write.completed',
460
+ identity: { projectRoot: this.projectRoot, sessionId: lease.sessionIds[0] },
461
+ level: 'info',
462
+ timestamp: Date.now(),
463
+ data: { stage: 'distill_batch', batchId, observationCount: validObs.length, eventsMarked: lease.eventIds.length },
459
464
  });
460
465
  return validObs.length;
461
466
  }
@@ -503,6 +508,7 @@ export class MemoryGate {
503
508
  observations: validObs,
504
509
  sourceEvents: [event],
505
510
  defaultSourceEventIds: [turnEventId],
511
+ evidenceText: readCanonicalPrompt(event.payload, event.user_message) || undefined,
506
512
  });
507
513
  if (capsules.rejected.length > 0 || capsules.accepted.length !== validObs.length) {
508
514
  throw new Error(`memory_commit_turn failed semantic capsule validation: ${JSON.stringify(capsules.rejected)}`);
@@ -579,6 +585,156 @@ export class MemoryGate {
579
585
  })();
580
586
  return saved;
581
587
  }
588
+ async refineExplicitTurnObservations(turnEventId, sessionId, observations) {
589
+ if (!this.engine)
590
+ await this.forceWarmUp();
591
+ const engine = this.requireEngine();
592
+ const event = engine.store.getEvent(turnEventId);
593
+ if (!event || event.session_id !== sessionId) {
594
+ throw new Error(`Memory turn event ${turnEventId} was not found for session ${sessionId}`);
595
+ }
596
+ const sourceText = readCanonicalPrompt(event.payload, event.user_message);
597
+ if (!sourceText)
598
+ throw new Error(`Memory turn event ${turnEventId} has no canonical user prompt`);
599
+ for (const observation of observations) {
600
+ validateEvidenceSpans(sourceText, observation.evidenceSpans ?? []);
601
+ }
602
+ const durableCandidates = observations.filter(observation => observation.disposition !== 'turn_constraint');
603
+ const turnConstraints = observations.filter(observation => observation.disposition === 'turn_constraint');
604
+ const validObs = durableCandidates.length > 0 ? validateObservationBatch(durableCandidates) : [];
605
+ if (!validObs)
606
+ throw new Error('Explicit memory refinement contains invalid durable candidates');
607
+ const capsules = validateMemoryCapsules({
608
+ projectRoot: this.projectRoot,
609
+ observations: validObs,
610
+ sourceEvents: [event],
611
+ defaultSourceEventIds: [turnEventId],
612
+ evidenceText: sourceText,
613
+ requireEvidenceSpans: true,
614
+ });
615
+ if (capsules.rejected.length > 0 || capsules.accepted.length !== validObs.length) {
616
+ throw new Error(`Explicit memory refinement failed evidence validation: ${JSON.stringify(capsules.rejected)}`);
617
+ }
618
+ const existingIds = new Set(engine.store.listObservations().map(observation => observation.id));
619
+ const turnId = eventTurnId(event, turnEventId);
620
+ const memories = [];
621
+ const retiredMemoryIds = [];
622
+ const operations = [];
623
+ this.getStoreDb().transaction(() => {
624
+ for (const acceptedCapsule of capsules.accepted) {
625
+ const observation = acceptedCapsule.observation;
626
+ const semantic = normalizeSemanticMemory({
627
+ project: this.projectRoot,
628
+ type: observation.type,
629
+ title: observation.title,
630
+ narrative: observation.narrative ?? '',
631
+ concepts: observation.concepts,
632
+ subject: observation.subject,
633
+ predicate: observation.predicate,
634
+ value: observation.value,
635
+ polarity: observation.polarity,
636
+ entities: observation.entities,
637
+ temporal: observation.temporal,
638
+ });
639
+ const write = engine.store.acceptSemanticObservation({
640
+ sessionId,
641
+ project: this.projectRoot,
642
+ type: observation.type,
643
+ title: observation.title,
644
+ narrative: observation.narrative,
645
+ facts: [...new Set([...(observation.facts ?? []), ...(observation.evidenceSpans ?? [])])],
646
+ concepts: mergeSemanticEntities(normalizeBatchConcepts(observation), [semantic.subject, semantic.predicate, semantic.value], semantic.entities.map(normalizeEntityKey)),
647
+ importance: Math.max(8, observation.importance),
648
+ confidence: Math.max(0.9, observation.confidence),
649
+ source: 'explicit_intent',
650
+ sourceEventIds: [turnEventId],
651
+ qualityStatus: 'accepted',
652
+ evidenceStatus: 'verified',
653
+ evidenceDetails: (observation.evidenceSpans ?? []).map(span => ({
654
+ kind: 'host_evidence_span',
655
+ source: turnEventId,
656
+ summary: span,
657
+ })),
658
+ semantic,
659
+ capsule: acceptedCapsule.capsule,
660
+ projection: acceptedCapsule.projection,
661
+ preferIncomingNarrative: true,
662
+ });
663
+ const row = write.row;
664
+ operations.push(write.action.toLocaleLowerCase('en-US'));
665
+ memories.push({
666
+ id: row.id,
667
+ type: row.type,
668
+ title: row.title,
669
+ content: row.narrative,
670
+ source: row.source,
671
+ scope: row.project,
672
+ confidence: row.confidence,
673
+ importance: row.importance,
674
+ score: 1,
675
+ createdAt: row.timestamp,
676
+ indexStatus: row.embedding ? 'indexed' : 'pending',
677
+ });
678
+ engine.store.recordPendingTurnObservation({
679
+ turnEventId,
680
+ turnId,
681
+ sessionId,
682
+ project: this.projectRoot,
683
+ observationId: row.id,
684
+ createdByTurn: !existingIds.has(row.id),
685
+ createdAt: event.created_at,
686
+ });
687
+ }
688
+ const constraintSpans = turnConstraints.flatMap(candidate => candidate.evidenceSpans ?? []);
689
+ const baseline = engine.store.listObservations({ project: this.projectRoot })
690
+ .filter(observation => observation.source === 'explicit_intent')
691
+ .filter(observation => parseLearningStringArray(observation.source_event_ids).includes(turnEventId));
692
+ const refinedIds = new Set(memories.map(memory => memory.id));
693
+ for (const observation of baseline) {
694
+ if (refinedIds.has(observation.id) || observation.resolution_state === 'superseded')
695
+ continue;
696
+ const replacementIndex = durableCandidates.findIndex(candidate => ((candidate.evidenceSpans ?? []).some(span => sameGroundedClause(observation.narrative, span))));
697
+ const replacement = replacementIndex >= 0
698
+ ? memories[replacementIndex]
699
+ : durableCandidates.length === 1 ? memories[0] : undefined;
700
+ if (!replacement || replacement.id === observation.id)
701
+ continue;
702
+ engine.store.setObservationResolution(observation.id, 'superseded', replacement.id);
703
+ engine.store.addSemanticObservationRelation(replacement.id, observation.id, 'supersedes');
704
+ retiredMemoryIds.push(observation.id);
705
+ operations.push('supersede');
706
+ }
707
+ for (const observation of baseline) {
708
+ if (observation.resolution_state === 'superseded' || retiredMemoryIds.includes(observation.id))
709
+ continue;
710
+ if (!constraintSpans.some(span => sameGroundedClause(observation.narrative, span)))
711
+ continue;
712
+ if (engine.store.quarantineObservation(observation.id, 'host_classified_turn_constraint', {
713
+ turnId,
714
+ eventId: turnEventId,
715
+ evidenceSpans: constraintSpans,
716
+ })) {
717
+ retiredMemoryIds.push(observation.id);
718
+ operations.push('retire');
719
+ }
720
+ }
721
+ })();
722
+ emitSemanticControlLog({
723
+ event: 'memory.semantic.refined',
724
+ identity: { projectRoot: this.projectRoot, sessionId, turnId },
725
+ level: 'info',
726
+ timestamp: Date.now(),
727
+ data: {
728
+ candidateCount: observations.length,
729
+ durableCount: durableCandidates.length,
730
+ turnConstraintCount: turnConstraints.length,
731
+ memoryIds: memories.map(memory => memory.id),
732
+ retiredMemoryIds,
733
+ operations,
734
+ },
735
+ });
736
+ return { memories, retiredMemoryIds, operations };
737
+ }
582
738
  async skipTurnEvent(turnEventId, sessionId, reason) {
583
739
  if (!this.engine)
584
740
  await this.forceWarmUp();
@@ -639,7 +795,13 @@ export class MemoryGate {
639
795
  * @deprecated Use saveDistilledObservationsByBatch(batchId, observations) instead.
640
796
  */
641
797
  async saveDistilledObservations(observations, sessionId) {
642
- console.warn('[MemoryGate] saveDistilledObservations by sessionId is deprecated; use saveDistilledObservationsByBatch instead');
798
+ emitSemanticControlLog({
799
+ event: 'memory.write.degraded',
800
+ identity: { projectRoot: this.projectRoot, sessionId },
801
+ level: 'warn',
802
+ timestamp: Date.now(),
803
+ data: { stage: 'legacy_distill', reason: 'deprecated_session_write', fallback: 'legacy_compatibility_path' },
804
+ });
643
805
  if (!this.engine)
644
806
  return 0;
645
807
  const validObs = validateObservationBatch(observations);
@@ -684,10 +846,12 @@ export class MemoryGate {
684
846
  engine.store.markEventsProcessed(unprocessedIds);
685
847
  });
686
848
  transaction();
687
- console.log('[MemoryGate] Distilled observations saved', {
688
- sessionId,
689
- observationCount: validObs.length,
690
- eventsMarked: unprocessedIds.length,
849
+ emitSemanticControlLog({
850
+ event: 'memory.write.completed',
851
+ identity: { projectRoot: this.projectRoot, sessionId },
852
+ level: 'info',
853
+ timestamp: Date.now(),
854
+ data: { stage: 'legacy_distill', observationCount: validObs.length, eventsMarked: unprocessedIds.length },
691
855
  });
692
856
  return validObs.length;
693
857
  }
@@ -716,11 +880,12 @@ export class MemoryGate {
716
880
  const events = this.engine.store.getUnprocessedEvents();
717
881
  if (events.length === 0)
718
882
  return 0;
719
- const groups = groupEventsBySession(events, { log: false });
883
+ const groups = groupEventsBySession(events, { log: false, projectRoot: this.projectRoot });
720
884
  const preserveSessionIds = this.getActiveSessionIds(groups);
721
885
  const { discarded } = triageGroupsWithDisposition(groups, Number.MAX_SAFE_INTEGER, {
722
886
  log: false,
723
887
  preserveSessionIds,
888
+ projectRoot: this.projectRoot,
724
889
  });
725
890
  const ids = discarded.flatMap(group => group.events).map(event => event.id);
726
891
  return this.engine.store.discardEvents(ids, 'low_value_auto_triage', `auto-triage:${Date.now()}`);
@@ -753,7 +918,10 @@ export class MemoryGate {
753
918
  this.projectValid = true;
754
919
  }
755
920
  this.state = 'warming';
756
- console.log('[MemoryGate] State transition', { from: 'cold', to: 'warming' });
921
+ emitSemanticControlLog({
922
+ event: 'memory.engine.transitioned', identity: { projectRoot: this.projectRoot }, level: 'info', timestamp: Date.now(),
923
+ data: { from: 'cold', to: 'warming' },
924
+ });
757
925
  this.engine = new MemoryEngine(this.projectRoot, this.embedder);
758
926
  this.setLeaseStore(new LeaseStore(this.getStoreDb()));
759
927
  // Clean up zombie sessions (>24h active)
@@ -761,7 +929,13 @@ export class MemoryGate {
761
929
  const zombies = this.engine.store.getZombieSessions(24);
762
930
  for (const z of zombies) {
763
931
  this.engine.store.closeSession(z.id);
764
- console.log('[MemoryGate] Zombie session auto-closed', { sessionId: z.id, age: `${Math.round((Date.now() - z.created_at) / 3600000)}h` });
932
+ emitSemanticControlLog({
933
+ event: 'memory.engine.transitioned',
934
+ identity: { projectRoot: this.projectRoot, sessionId: z.id },
935
+ level: 'warn',
936
+ timestamp: Date.now(),
937
+ data: { from: 'active', to: 'closed', reason: 'zombie_session', ageHours: Math.round((Date.now() - z.created_at) / 3600000) },
938
+ });
765
939
  }
766
940
  }
767
941
  catch { /* non-critical */ }
@@ -771,7 +945,10 @@ export class MemoryGate {
771
945
  }
772
946
  catch { /* non-critical */ }
773
947
  this.state = 'hot';
774
- console.log('[MemoryGate] State transition', { from: 'warming', to: 'hot' });
948
+ emitSemanticControlLog({
949
+ event: 'memory.engine.transitioned', identity: { projectRoot: this.projectRoot }, level: 'info', timestamp: Date.now(),
950
+ data: { from: 'warming', to: 'hot' },
951
+ });
775
952
  }
776
953
  // --- IMemoryEngine implementation (delegates to engine when hot) ---
777
954
  requireEngine() {
@@ -854,7 +1031,28 @@ export class MemoryGate {
854
1031
  async saveExplicitMemoryIntent(content, sessionId, sourceEventId) {
855
1032
  if (!this.engine)
856
1033
  await this.forceWarmUp();
857
- return this.requireEngine().saveExplicitMemoryIntent(content, sessionId, sourceEventId);
1034
+ try {
1035
+ const result = await this.requireEngine().saveExplicitMemoryIntent(content, sessionId, sourceEventId);
1036
+ emitSemanticControlLog({
1037
+ event: 'memory.write.completed',
1038
+ identity: { projectRoot: this.projectRoot, sessionId },
1039
+ level: 'info',
1040
+ timestamp: Date.now(),
1041
+ data: {
1042
+ stage: 'explicit_memory', memoryId: result.id, relatedMemoryIds: result.relatedMemoryIds ?? [],
1043
+ semanticStatus: result.semanticDecision?.status,
1044
+ operations: result.semanticDecision?.operations ?? [],
1045
+ },
1046
+ });
1047
+ return result;
1048
+ }
1049
+ catch (error) {
1050
+ emitSemanticControlLog({
1051
+ event: 'memory.write.degraded', identity: { projectRoot: this.projectRoot, sessionId }, level: 'error', timestamp: Date.now(),
1052
+ data: { stage: 'explicit_memory', reason: memoryErrorReason(error) },
1053
+ });
1054
+ throw error;
1055
+ }
858
1056
  }
859
1057
  async enrichExplicitMemoryObservation(observationId) {
860
1058
  if (!this.engine)
@@ -924,15 +1122,28 @@ export class MemoryGate {
924
1122
  const rows = engine.store.listObservationsMissingEmbedding(batchSize, lastId);
925
1123
  if (rows.length === 0)
926
1124
  break;
927
- const embeddings = await this.embedder.embedBatch(rows.map(row => `${row.title}\n${row.narrative}`));
928
- if (embeddings.length !== rows.length) {
929
- throw new Error(`Observation vector count mismatch: ${rows.length} observations, ${embeddings.length} vectors`);
1125
+ const texts = rows.map(row => `${row.title}\n${row.narrative}`);
1126
+ let indexed = [];
1127
+ try {
1128
+ const embeddings = await this.embedder.embedBatch(texts);
1129
+ if (embeddings.length !== rows.length) {
1130
+ throw new Error(`Observation vector count mismatch: ${rows.length} observations, ${embeddings.length} vectors`);
1131
+ }
1132
+ indexed = rows.map((row, index) => ({ id: row.id, embedding: embeddings[index] }));
1133
+ }
1134
+ catch {
1135
+ for (let index = 0; index < rows.length; index++) {
1136
+ try {
1137
+ indexed.push({ id: rows[index].id, embedding: await this.embedder.embed(texts[index]) });
1138
+ }
1139
+ catch {
1140
+ // Keep this observation pending; deterministic retrieval remains available.
1141
+ }
1142
+ }
930
1143
  }
931
- engine.store.insertObservationEmbeddings(rows.map((row, index) => ({
932
- id: row.id,
933
- embedding: embeddings[index],
934
- })));
935
- processed += rows.length;
1144
+ if (indexed.length > 0)
1145
+ engine.store.insertObservationEmbeddings(indexed);
1146
+ processed += indexed.length;
936
1147
  lastId = rows.at(-1).id;
937
1148
  const coverage = engine.store.countObservationVectors();
938
1149
  options.onProgress?.({ processed, remaining: coverage.missing, lastId });
@@ -964,7 +1175,38 @@ export class MemoryGate {
964
1175
  async search(query, options) {
965
1176
  if (!this.engine)
966
1177
  await this.forceWarmUp();
967
- return this.requireEngine().search(query, options);
1178
+ const startedAt = Date.now();
1179
+ try {
1180
+ const results = await this.requireEngine().search(query, options);
1181
+ emitSemanticControlLog({
1182
+ event: 'memory.search.completed',
1183
+ identity: {
1184
+ projectRoot: this.projectRoot,
1185
+ sessionId: options?.sessionId,
1186
+ executionId: options?.executionId,
1187
+ requestId: options?.requestId,
1188
+ },
1189
+ level: 'info',
1190
+ timestamp: Date.now(),
1191
+ data: {
1192
+ status: results.length > 0 ? 'selected' : 'empty', selected: results.length,
1193
+ verified: results.filter(result => result.entry.status === 'approved').length,
1194
+ durationMs: Date.now() - startedAt,
1195
+ vectorReady: results.filter(result => (result.scoreDetails?.semanticScore ?? 0) > 0).length,
1196
+ },
1197
+ });
1198
+ return results;
1199
+ }
1200
+ catch (error) {
1201
+ emitSemanticControlLog({
1202
+ event: 'memory.search.completed',
1203
+ identity: { projectRoot: this.projectRoot, sessionId: options?.sessionId, executionId: options?.executionId, requestId: options?.requestId },
1204
+ level: 'error',
1205
+ timestamp: Date.now(),
1206
+ data: { status: 'failed', selected: 0, durationMs: Date.now() - startedAt, reason: memoryErrorReason(error) },
1207
+ });
1208
+ throw error;
1209
+ }
968
1210
  }
969
1211
  async listMemories(filter) {
970
1212
  if (!this.engine)
@@ -1070,6 +1312,24 @@ function eventTurnId(event, fallbackEventId) {
1070
1312
  }
1071
1313
  return `turn:${fallbackEventId}`;
1072
1314
  }
1315
+ function readCanonicalPrompt(payloadJson, userMessage) {
1316
+ if (payloadJson) {
1317
+ try {
1318
+ const payload = JSON.parse(payloadJson);
1319
+ if (typeof payload.prompt === 'string' && payload.prompt.trim())
1320
+ return payload.prompt.trim();
1321
+ }
1322
+ catch { }
1323
+ }
1324
+ return userMessage?.trim() ?? '';
1325
+ }
1326
+ function sameGroundedClause(narrative, evidenceSpan) {
1327
+ const left = narrative.normalize('NFKC').trim().toLocaleLowerCase('en-US');
1328
+ const right = evidenceSpan.normalize('NFKC').trim().toLocaleLowerCase('en-US');
1329
+ if (left.length < 4 || right.length < 4)
1330
+ return false;
1331
+ return left === right || (right.includes(left) && left.length / right.length >= 0.7);
1332
+ }
1073
1333
  // --- L1 Rule Scorer (backward compat) ---
1074
1334
  function scoreL1Rules(event) {
1075
1335
  let score = 0;
@@ -1131,5 +1391,8 @@ function scoreDialogSignals(message) {
1131
1391
  }
1132
1392
  return maxScore;
1133
1393
  }
1394
+ function memoryErrorReason(error) {
1395
+ return (error instanceof Error ? error.message : String(error)).trim().slice(0, 500) || 'memory_operation_failed';
1396
+ }
1134
1397
  export { scoreL1Rules, scoreDialogSignals };
1135
1398
  //# sourceMappingURL=memory-gate.js.map