@rlabs-inc/memory 0.3.10 → 0.4.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/src/core/store.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import { createDatabase, type Database, type PersistentCollection } from '@rlabs-inc/fsdb'
7
7
  import { homedir } from 'os'
8
8
  import { join } from 'path'
9
+ import { logger } from '../utils/logger.ts'
9
10
  import {
10
11
  type CuratedMemory,
11
12
  type StoredMemory,
@@ -126,7 +127,7 @@ export class MemoryStore {
126
127
 
127
128
  // Use the configured global path (always central)
128
129
  const globalPath = this._config.globalPath
129
- console.log(`🌐 [DEBUG] Initializing global database at ${globalPath}`)
130
+ logger.debug(`Initializing global database at ${globalPath}`, 'store')
130
131
 
131
132
  const db = createDatabase({
132
133
  name: 'global',
@@ -176,9 +177,7 @@ export class MemoryStore {
176
177
  importance_weight: record.importance_weight,
177
178
  confidence_score: record.confidence_score,
178
179
  context_type: record.context_type as StoredMemory['context_type'],
179
- temporal_relevance: record.temporal_relevance as StoredMemory['temporal_relevance'],
180
- knowledge_domain: record.knowledge_domain as StoredMemory['knowledge_domain'],
181
- emotional_resonance: record.emotional_resonance as StoredMemory['emotional_resonance'],
180
+ temporal_class: record.temporal_class as StoredMemory['temporal_class'],
182
181
  action_required: record.action_required,
183
182
  problem_solution_pair: record.problem_solution_pair,
184
183
  semantic_tags: record.semantic_tags,
@@ -210,30 +209,28 @@ export class MemoryStore {
210
209
  const typeDefaults = V2_DEFAULTS.typeDefaults[contextType] ?? V2_DEFAULTS.typeDefaults.personal
211
210
 
212
211
  const id = memories.insert({
213
- // Core v1 fields
212
+ // Core fields
214
213
  content: memory.content,
215
214
  reasoning: memory.reasoning,
216
215
  importance_weight: memory.importance_weight,
217
216
  confidence_score: memory.confidence_score,
218
217
  context_type: memory.context_type,
219
- temporal_relevance: memory.temporal_relevance,
220
- knowledge_domain: memory.knowledge_domain,
221
- emotional_resonance: memory.emotional_resonance,
218
+ temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? 'eternal',
222
219
  action_required: memory.action_required,
223
220
  problem_solution_pair: memory.problem_solution_pair,
224
221
  semantic_tags: memory.semantic_tags,
225
222
  trigger_phrases: memory.trigger_phrases,
226
223
  question_types: memory.question_types,
224
+ anti_triggers: memory.anti_triggers ?? [],
227
225
  session_id: sessionId,
228
226
  project_id: 'global',
229
227
  embedding: embedding
230
228
  ? (embedding instanceof Float32Array ? embedding : new Float32Array(embedding))
231
229
  : null,
232
230
 
233
- // v2 lifecycle fields - global memories are always scope: 'global'
231
+ // Lifecycle fields - global memories are always scope: 'global'
234
232
  status: V2_DEFAULTS.fallback.status,
235
233
  scope: 'global', // Always global for global memories
236
- temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? 'eternal',
237
234
  fade_rate: typeDefaults?.fade_rate ?? 0, // Global memories typically don't fade
238
235
  session_created: sessionNumber ?? 0,
239
236
  session_updated: sessionNumber ?? 0,
@@ -243,18 +240,15 @@ export class MemoryStore {
243
240
  related_files: memory.related_files ?? [],
244
241
  awaiting_implementation: memory.awaiting_implementation ?? false,
245
242
  awaiting_decision: memory.awaiting_decision ?? false,
246
- retrieval_weight: memory.importance_weight,
247
243
  exclude_from_retrieval: false,
248
244
  schema_version: MEMORY_SCHEMA_VERSION,
249
245
 
250
- // Initialize empty relationship fields
246
+ // Relationship fields
251
247
  supersedes: null,
252
248
  superseded_by: null,
253
249
  related_to: [],
254
250
  resolves: [],
255
251
  resolved_by: null,
256
- parent_id: null,
257
- child_ids: [],
258
252
  blocked_by: null,
259
253
  blocks: [],
260
254
  })
@@ -414,13 +408,13 @@ export class MemoryStore {
414
408
  */
415
409
  async getProject(projectId: string): Promise<ProjectDB> {
416
410
  if (this._projects.has(projectId)) {
417
- console.log(`🔄 [DEBUG] Returning cached databases for ${projectId}`)
411
+ logger.debug(`Returning cached databases for ${projectId}`, 'store')
418
412
  return this._projects.get(projectId)!
419
413
  }
420
414
 
421
- console.log(`🆕 [DEBUG] Creating NEW databases for ${projectId}`)
415
+ logger.debug(`Creating NEW databases for ${projectId}`, 'store')
422
416
  const projectPath = join(this._config.basePath, projectId)
423
- console.log(` Path: ${projectPath}`)
417
+ logger.debug(`Path: ${projectPath}`, 'store')
424
418
 
425
419
  // Create the database for this project
426
420
  const db = createDatabase({
@@ -490,30 +484,28 @@ export class MemoryStore {
490
484
  const typeDefaults = V2_DEFAULTS.typeDefaults[contextType] ?? V2_DEFAULTS.typeDefaults.technical
491
485
 
492
486
  const id = memories.insert({
493
- // Core v1 fields
487
+ // Core fields
494
488
  content: memory.content,
495
489
  reasoning: memory.reasoning,
496
490
  importance_weight: memory.importance_weight,
497
491
  confidence_score: memory.confidence_score,
498
492
  context_type: memory.context_type,
499
- temporal_relevance: memory.temporal_relevance,
500
- knowledge_domain: memory.knowledge_domain,
501
- emotional_resonance: memory.emotional_resonance,
493
+ temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? V2_DEFAULTS.fallback.temporal_class,
502
494
  action_required: memory.action_required,
503
495
  problem_solution_pair: memory.problem_solution_pair,
504
496
  semantic_tags: memory.semantic_tags,
505
497
  trigger_phrases: memory.trigger_phrases,
506
498
  question_types: memory.question_types,
499
+ anti_triggers: memory.anti_triggers ?? [],
507
500
  session_id: sessionId,
508
501
  project_id: projectId,
509
502
  embedding: embedding
510
503
  ? (embedding instanceof Float32Array ? embedding : new Float32Array(embedding))
511
504
  : null,
512
505
 
513
- // v2 lifecycle fields - use curator-provided values or smart defaults
506
+ // Lifecycle fields - use curator-provided values or smart defaults
514
507
  status: V2_DEFAULTS.fallback.status,
515
508
  scope: memory.scope ?? typeDefaults?.scope ?? V2_DEFAULTS.fallback.scope,
516
- temporal_class: memory.temporal_class ?? typeDefaults?.temporal_class ?? V2_DEFAULTS.fallback.temporal_class,
517
509
  fade_rate: typeDefaults?.fade_rate ?? V2_DEFAULTS.fallback.fade_rate,
518
510
  session_created: sessionNumber ?? 0,
519
511
  session_updated: sessionNumber ?? 0,
@@ -523,18 +515,15 @@ export class MemoryStore {
523
515
  related_files: memory.related_files ?? [],
524
516
  awaiting_implementation: memory.awaiting_implementation ?? false,
525
517
  awaiting_decision: memory.awaiting_decision ?? false,
526
- retrieval_weight: memory.importance_weight, // Start with importance as retrieval weight
527
518
  exclude_from_retrieval: false,
528
519
  schema_version: MEMORY_SCHEMA_VERSION,
529
520
 
530
- // Initialize empty relationship fields
521
+ // Relationship fields
531
522
  supersedes: null,
532
523
  superseded_by: null,
533
524
  related_to: [],
534
525
  resolves: [],
535
526
  resolved_by: null,
536
- parent_id: null,
537
- child_ids: [],
538
527
  blocked_by: null,
539
528
  blocks: [],
540
529
  })
@@ -555,9 +544,7 @@ export class MemoryStore {
555
544
  importance_weight: record.importance_weight,
556
545
  confidence_score: record.confidence_score,
557
546
  context_type: record.context_type as StoredMemory['context_type'],
558
- temporal_relevance: record.temporal_relevance as StoredMemory['temporal_relevance'],
559
- knowledge_domain: record.knowledge_domain as StoredMemory['knowledge_domain'],
560
- emotional_resonance: record.emotional_resonance as StoredMemory['emotional_resonance'],
547
+ temporal_class: record.temporal_class as StoredMemory['temporal_class'],
561
548
  action_required: record.action_required,
562
549
  problem_solution_pair: record.problem_solution_pair,
563
550
  semantic_tags: record.semantic_tags,
@@ -595,9 +582,7 @@ export class MemoryStore {
595
582
  importance_weight: record.importance_weight,
596
583
  confidence_score: record.confidence_score,
597
584
  context_type: record.context_type as StoredMemory['context_type'],
598
- temporal_relevance: record.temporal_relevance as StoredMemory['temporal_relevance'],
599
- knowledge_domain: record.knowledge_domain as StoredMemory['knowledge_domain'],
600
- emotional_resonance: record.emotional_resonance as StoredMemory['emotional_resonance'],
585
+ temporal_class: record.temporal_class as StoredMemory['temporal_class'],
601
586
  action_required: record.action_required,
602
587
  problem_solution_pair: record.problem_solution_pair,
603
588
  semantic_tags: record.semantic_tags,
@@ -619,9 +604,7 @@ export class MemoryStore {
619
604
  importance_weight: result.record.importance_weight,
620
605
  confidence_score: result.record.confidence_score,
621
606
  context_type: result.record.context_type as StoredMemory['context_type'],
622
- temporal_relevance: result.record.temporal_relevance as StoredMemory['temporal_relevance'],
623
- knowledge_domain: result.record.knowledge_domain as StoredMemory['knowledge_domain'],
624
- emotional_resonance: result.record.emotional_resonance as StoredMemory['emotional_resonance'],
607
+ temporal_class: result.record.temporal_class as StoredMemory['temporal_class'],
625
608
  action_required: result.record.action_required,
626
609
  problem_solution_pair: result.record.problem_solution_pair,
627
610
  semantic_tags: result.record.semantic_tags,
@@ -743,9 +726,7 @@ export class MemoryStore {
743
726
  ): Promise<string> {
744
727
  const { summaries } = await this.getProject(projectId)
745
728
 
746
- console.log(`📝 [DEBUG] Storing summary for ${projectId}:`)
747
- console.log(` Summary length: ${summary.length} chars`)
748
- console.log(` Summaries count before: ${summaries.all().length}`)
729
+ logger.debug(`Storing summary for ${projectId}: ${summary.length} chars`, 'store')
749
730
 
750
731
  const id = summaries.insert({
751
732
  session_id: sessionId,
@@ -754,8 +735,7 @@ export class MemoryStore {
754
735
  interaction_tone: interactionTone,
755
736
  })
756
737
 
757
- console.log(` Summaries count after: ${summaries.all().length}`)
758
- console.log(` Inserted ID: ${id}`)
738
+ logger.debug(`Summary stored with ID: ${id}`, 'store')
759
739
 
760
740
  return id
761
741
  }
@@ -766,14 +746,14 @@ export class MemoryStore {
766
746
  async getLatestSummary(projectId: string): Promise<SessionSummary | null> {
767
747
  const { summaries } = await this.getProject(projectId)
768
748
 
769
- console.log(`📖 [DEBUG] Getting latest summary for ${projectId}:`)
749
+ logger.debug(`Getting latest summary for ${projectId}`, 'store')
770
750
  const all = summaries.all()
771
- console.log(` Summaries found: ${all.length}`)
772
751
 
773
752
  if (!all.length) {
774
- console.log(` No summaries found!`)
753
+ logger.debug(`No summaries found for ${projectId}`, 'store')
775
754
  return null
776
755
  }
756
+ logger.debug(`Found ${all.length} summaries for ${projectId}`, 'store')
777
757
 
778
758
  // Sort by created timestamp (most recent first)
779
759
  const sorted = [...all].sort((a, b) => b.created - a.created)
@@ -0,0 +1,392 @@
1
+ // ============================================================================
2
+ // V3 SCHEMA MIGRATION
3
+ // Consolidates fragmented metadata into canonical categories
4
+ // ============================================================================
5
+
6
+ /**
7
+ * V3 Schema Version
8
+ */
9
+ export const V3_SCHEMA_VERSION = 3
10
+
11
+ /**
12
+ * Canonical context types - STRICT ENUM, no custom strings allowed
13
+ */
14
+ export const CANONICAL_CONTEXT_TYPES = [
15
+ 'technical', // Code, implementation, APIs, how things work
16
+ 'debug', // Bugs, errors, fixes, gotchas, troubleshooting
17
+ 'architecture', // System design, patterns, structure, decisions about structure
18
+ 'decision', // Choices made and reasoning, trade-offs
19
+ 'personal', // Relationship, family, preferences, collaboration style
20
+ 'philosophy', // Beliefs, values, worldview, principles
21
+ 'workflow', // How we work together, processes, habits
22
+ 'milestone', // Achievements, completions, shipped features
23
+ 'breakthrough', // Major discoveries, aha moments, key insights
24
+ 'unresolved', // Open questions, investigations, todos, blockers
25
+ 'state', // Current project status, what's working/broken now
26
+ ] as const
27
+
28
+ export type CanonicalContextType = typeof CANONICAL_CONTEXT_TYPES[number]
29
+
30
+ /**
31
+ * Mapping from ALL known fragmented types to canonical types
32
+ * Built from analysis of 1,278 memories with 170+ unique context_type values
33
+ */
34
+ export const CONTEXT_TYPE_MIGRATION_MAP: Record<string, CanonicalContextType> = {
35
+ // === TECHNICAL (code, implementation, APIs) ===
36
+ 'technical': 'technical',
37
+ 'technical_implementation': 'technical',
38
+ 'technical_pattern': 'technical',
39
+ 'technical_solution': 'technical',
40
+ 'technical_insight': 'technical',
41
+ 'technical_reference': 'technical',
42
+ 'technical_achievement': 'technical',
43
+ 'technical_discovery': 'technical',
44
+ 'technical_milestone': 'technical',
45
+ 'technical_verification': 'technical',
46
+ 'technical_validation': 'technical',
47
+ 'technical_research': 'technical',
48
+ 'technical_metrics': 'technical',
49
+ 'technical_knowledge': 'technical',
50
+ 'technical_improvement': 'technical',
51
+ 'technical_connection': 'technical',
52
+ 'technical_completion': 'technical',
53
+ 'technical_clarification': 'technical',
54
+ 'technical_blocker': 'technical',
55
+ 'technical-pattern': 'technical',
56
+ 'technical-solution': 'technical',
57
+ 'implementation': 'technical',
58
+ 'implementation_detail': 'technical',
59
+ 'implementation_pattern': 'technical',
60
+ 'implementation_task': 'technical',
61
+ 'implementation_status': 'technical',
62
+ 'implementation_reference': 'technical',
63
+ 'implementation_breakthrough': 'technical',
64
+ 'implementation-gap': 'technical',
65
+ 'implementation-detail': 'technical',
66
+ 'implementation-complete': 'technical',
67
+ 'feature_implementation': 'technical',
68
+ 'feature_specification': 'technical',
69
+ 'feature_reference': 'technical',
70
+ 'feature_design': 'technical',
71
+ 'code_reference': 'technical',
72
+ 'code_quality': 'technical',
73
+ 'code_patterns': 'technical',
74
+ 'code_pattern': 'technical',
75
+ 'coding_pattern': 'technical',
76
+ 'api_reference': 'technical',
77
+ 'api_migration': 'technical',
78
+ 'api_limitation': 'technical',
79
+ 'api_documentation': 'technical',
80
+ 'api_design': 'technical',
81
+ 'api_configuration': 'technical',
82
+ 'api_clarification': 'technical',
83
+ 'reference': 'technical',
84
+ 'reference_implementation': 'technical',
85
+ 'domain_knowledge': 'technical',
86
+ 'configuration': 'technical',
87
+ 'deployment_configuration': 'technical',
88
+ 'tool_knowledge': 'technical',
89
+ 'tool_created': 'technical',
90
+ 'problem_solution': 'technical', // Has problem_solution_pair flag
91
+ 'general': 'technical', // Fallback
92
+ 'insight': 'technical',
93
+ 'validation': 'technical',
94
+ 'validation_results': 'technical',
95
+
96
+ // === DEBUG (bugs, errors, fixes) ===
97
+ 'debug': 'debug',
98
+ 'debugging': 'debug',
99
+ 'debugging_insight': 'debug',
100
+ 'debugging_technique': 'debug',
101
+ 'debugging_tool': 'debug',
102
+ 'debugging_context': 'debug',
103
+ 'active_debugging': 'debug',
104
+ 'bug_fix': 'debug',
105
+ 'bug_solution': 'debug',
106
+ 'bug_report': 'debug',
107
+ 'bug_fix_pattern': 'debug',
108
+ 'bug_fix_needed': 'debug',
109
+ 'bug_discovery': 'debug',
110
+ 'active_bug': 'debug',
111
+ 'active_issue': 'debug',
112
+ 'technical_gotcha': 'debug',
113
+ 'gotcha': 'debug',
114
+ 'problem_discovery': 'debug',
115
+ 'technical_fix': 'debug',
116
+ 'solved': 'debug',
117
+ 'critical_discovery': 'debug',
118
+
119
+ // === ARCHITECTURE (system design, patterns) ===
120
+ 'architecture': 'architecture',
121
+ 'architectural': 'architecture',
122
+ 'architectural_decision': 'architecture',
123
+ 'architectural_pattern': 'architecture',
124
+ 'architectural_principle': 'architecture',
125
+ 'architectural_insight': 'architecture',
126
+ 'architectural_understanding': 'architecture',
127
+ 'architectural_discovery': 'architecture',
128
+ 'architectural_direction': 'architecture',
129
+ 'architectural-decision': 'architecture',
130
+ 'architecture_pattern': 'architecture',
131
+ 'architecture_decision': 'architecture',
132
+ 'architecture_documentation': 'architecture',
133
+ 'system_design': 'architecture',
134
+ 'design_pattern': 'architecture',
135
+ 'design_methodology': 'architecture',
136
+ 'design_principle': 'architecture',
137
+ 'design_insight': 'architecture',
138
+ 'design_system': 'architecture',
139
+ 'design-system': 'architecture',
140
+ 'core_concept': 'architecture',
141
+
142
+ // === DECISION (choices made and reasoning) ===
143
+ 'decision': 'decision',
144
+ 'technical_decision': 'decision',
145
+ 'design_decision': 'decision',
146
+ 'design-decision': 'decision',
147
+ 'design_philosophy': 'decision',
148
+ 'strategic_decision': 'decision',
149
+ 'strategic_priority': 'decision',
150
+ 'project-decision': 'decision',
151
+
152
+ // === PERSONAL (relationship, family, preferences) ===
153
+ 'personal': 'personal',
154
+ 'personal_context': 'personal',
155
+ 'personal_update': 'personal',
156
+ 'personal_philosophy': 'personal',
157
+ 'relationship': 'personal',
158
+ 'relationship_context': 'personal',
159
+ 'relationship_pattern': 'personal',
160
+ 'relationship_insight': 'personal',
161
+ 'relationship_philosophy': 'personal',
162
+ 'relationship_milestone': 'personal',
163
+ 'relationship_moment': 'personal',
164
+ 'relationship_dynamics': 'personal',
165
+ 'relationship_dynamic': 'personal',
166
+ 'relationship_repair': 'personal',
167
+ 'preference': 'personal',
168
+ 'user_preference': 'personal',
169
+ 'collaborator_philosophy': 'personal',
170
+ 'collaborator-guidance': 'personal',
171
+ 'collaboration_philosophy': 'personal',
172
+ 'collaboration_pattern': 'personal',
173
+ 'collaboration_insight': 'personal',
174
+ 'collaboration_context': 'personal',
175
+ 'collaboration': 'personal',
176
+ 'user_philosophy': 'personal',
177
+
178
+ // === PHILOSOPHY (beliefs, values, worldview) ===
179
+ 'philosophy': 'philosophy',
180
+ 'philosophical_insight': 'philosophy',
181
+ 'philosophical_framework': 'philosophy',
182
+ 'philosophical_anchor': 'philosophy',
183
+ 'philosophical_technical_bridge': 'philosophy',
184
+ 'project_philosophy': 'philosophy',
185
+ 'product_philosophy': 'philosophy',
186
+ 'values_and_ethics': 'philosophy',
187
+ 'wisdom': 'philosophy',
188
+
189
+ // === WORKFLOW (how we work together) ===
190
+ 'workflow': 'workflow',
191
+ 'workflow_pattern': 'workflow',
192
+ 'development_workflow': 'workflow',
193
+ 'development_tip': 'workflow',
194
+ 'developer_preferences': 'workflow',
195
+ 'development_practice': 'workflow',
196
+ 'development_methodology': 'workflow',
197
+
198
+ // === MILESTONE (achievements, completions) ===
199
+ 'milestone': 'milestone',
200
+ 'project_milestone': 'milestone',
201
+ 'creative_achievement': 'milestone',
202
+ 'technical_achievement': 'milestone',
203
+
204
+ // === BREAKTHROUGH (major discoveries) ===
205
+ 'breakthrough': 'breakthrough',
206
+
207
+ // === UNRESOLVED (open questions, todos) ===
208
+ 'unresolved': 'unresolved',
209
+ 'todo': 'unresolved',
210
+ 'open_question': 'unresolved',
211
+ 'open_questions': 'unresolved',
212
+ 'open_investigation': 'unresolved',
213
+ 'pending_work': 'unresolved',
214
+ 'pending_task': 'unresolved',
215
+ 'planned_experiment': 'unresolved',
216
+ 'work_in_progress': 'unresolved',
217
+ 'upcoming_work': 'unresolved',
218
+ 'future_planning': 'unresolved',
219
+ 'planning': 'unresolved',
220
+
221
+ // === STATE (current project status) ===
222
+ 'state': 'state',
223
+ 'technical_state': 'state',
224
+ 'project_status': 'state',
225
+ 'project_state': 'state',
226
+ 'project_context': 'state',
227
+ 'project_structure': 'state',
228
+ 'project_roadmap': 'state',
229
+ 'project_organization': 'state',
230
+ 'project_foundation': 'state',
231
+ 'project_direction': 'state',
232
+ 'project_vision': 'state',
233
+ 'project_documentation': 'state',
234
+ 'project_architecture': 'state',
235
+ 'experimental_result': 'state',
236
+ 'performance_data': 'state',
237
+ 'framework_status': 'state',
238
+ }
239
+
240
+ /**
241
+ * New emoji map aligned with canonical types
242
+ */
243
+ export const V3_EMOJI_MAP: Record<CanonicalContextType, string> = {
244
+ technical: '🔧', // Wrench - building/fixing
245
+ debug: '🐛', // Bug - debugging
246
+ architecture: '🏗️', // Construction - system design
247
+ decision: '⚖️', // Scale - weighing options
248
+ personal: '💜', // Purple heart - relationship
249
+ philosophy: '🌀', // Spiral - deeper thinking
250
+ workflow: '🔄', // Cycle - processes
251
+ milestone: '🏆', // Trophy - achievement
252
+ breakthrough: '💡', // Lightbulb - insight
253
+ unresolved: '❓', // Question - open items
254
+ state: '📍', // Pin - current status
255
+ }
256
+
257
+ /**
258
+ * Fields to DELETE in v3 migration
259
+ */
260
+ export const V3_DELETED_FIELDS = [
261
+ 'emotional_resonance', // 580 variants, broken matching
262
+ 'knowledge_domain', // Redundant with domain
263
+ 'component', // Always empty
264
+ 'prerequisite_understanding', // Never used
265
+ 'follow_up_context', // Never used
266
+ 'dependency_context', // Never used
267
+ 'retrieval_weight', // Retrieval uses importance_weight
268
+ 'parent_id', // No logic implemented
269
+ 'child_ids', // No logic implemented
270
+ 'expires_after_sessions', // Never used
271
+ 'temporal_relevance', // Replaced by temporal_class
272
+ ] as const
273
+
274
+ /**
275
+ * Map old temporal_relevance values to temporal_class
276
+ * Used during migration before deleting temporal_relevance
277
+ */
278
+ export const TEMPORAL_RELEVANCE_TO_CLASS: Record<string, string> = {
279
+ 'persistent': 'long_term', // Always relevant → fades slowly
280
+ 'session': 'short_term', // Session-specific → fades quickly
281
+ 'temporary': 'ephemeral', // Short-term → surface once then expire
282
+ 'archived': 'medium_term', // Historical → normal fade (status handles archival)
283
+ }
284
+
285
+ /**
286
+ * Migrate temporal_relevance to temporal_class
287
+ */
288
+ export function migrateTemporalRelevance(temporal_relevance: string | null | undefined): string | null {
289
+ if (!temporal_relevance) return null
290
+ return TEMPORAL_RELEVANCE_TO_CLASS[temporal_relevance] ?? null
291
+ }
292
+
293
+ /**
294
+ * V2 defaults updated for v3 canonical types
295
+ */
296
+ export const V3_TYPE_DEFAULTS: Record<CanonicalContextType, {
297
+ scope: 'global' | 'project'
298
+ temporal_class: 'eternal' | 'long_term' | 'medium_term' | 'short_term' | 'ephemeral'
299
+ fade_rate: number
300
+ }> = {
301
+ personal: { scope: 'global', temporal_class: 'eternal', fade_rate: 0 },
302
+ philosophy: { scope: 'global', temporal_class: 'eternal', fade_rate: 0 },
303
+ breakthrough: { scope: 'project', temporal_class: 'eternal', fade_rate: 0 },
304
+ decision: { scope: 'project', temporal_class: 'long_term', fade_rate: 0 },
305
+ milestone: { scope: 'project', temporal_class: 'eternal', fade_rate: 0 },
306
+ architecture: { scope: 'project', temporal_class: 'long_term', fade_rate: 0.01 },
307
+ workflow: { scope: 'project', temporal_class: 'long_term', fade_rate: 0.02 },
308
+ technical: { scope: 'project', temporal_class: 'medium_term', fade_rate: 0.03 },
309
+ debug: { scope: 'project', temporal_class: 'medium_term', fade_rate: 0.03 },
310
+ unresolved: { scope: 'project', temporal_class: 'medium_term', fade_rate: 0.05 },
311
+ state: { scope: 'project', temporal_class: 'short_term', fade_rate: 0.1 },
312
+ }
313
+
314
+ /**
315
+ * Check if a context_type is in our known mapping
316
+ */
317
+ export function isKnownContextType(type: string | undefined | null): boolean {
318
+ if (!type) return false
319
+ const normalized = type.toLowerCase().trim()
320
+ return normalized in CONTEXT_TYPE_MIGRATION_MAP || CANONICAL_CONTEXT_TYPES.includes(normalized as CanonicalContextType)
321
+ }
322
+
323
+ /**
324
+ * Migrate a context_type value to its canonical form
325
+ * @param oldType - The old context type value
326
+ * @param preserveUnknown - If true, return null for unknown types (caller can decide to keep original)
327
+ */
328
+ export function migrateContextType(oldType: string | undefined | null, preserveUnknown = false): CanonicalContextType | null {
329
+ if (!oldType) return 'technical'
330
+
331
+ const normalized = oldType.toLowerCase().trim()
332
+
333
+ // Already canonical
334
+ if (CANONICAL_CONTEXT_TYPES.includes(normalized as CanonicalContextType)) {
335
+ return normalized as CanonicalContextType
336
+ }
337
+
338
+ // Direct lookup in migration map
339
+ if (normalized in CONTEXT_TYPE_MIGRATION_MAP) {
340
+ return CONTEXT_TYPE_MIGRATION_MAP[normalized]
341
+ }
342
+
343
+ // If preserveUnknown is set, return null so caller can keep original
344
+ if (preserveUnknown) {
345
+ return null
346
+ }
347
+
348
+ // Fuzzy matching for unknown types
349
+ if (normalized.includes('debug') || normalized.includes('bug') || normalized.includes('fix')) {
350
+ return 'debug'
351
+ }
352
+ if (normalized.includes('architect') || normalized.includes('design') || normalized.includes('pattern')) {
353
+ return 'architecture'
354
+ }
355
+ if (normalized.includes('decision') || normalized.includes('choice')) {
356
+ return 'decision'
357
+ }
358
+ if (normalized.includes('personal') || normalized.includes('relationship') || normalized.includes('preference')) {
359
+ return 'personal'
360
+ }
361
+ if (normalized.includes('philosoph') || normalized.includes('value') || normalized.includes('wisdom')) {
362
+ return 'philosophy'
363
+ }
364
+ if (normalized.includes('workflow') || normalized.includes('process')) {
365
+ return 'workflow'
366
+ }
367
+ if (normalized.includes('milestone') || normalized.includes('achievement') || normalized.includes('complete')) {
368
+ return 'milestone'
369
+ }
370
+ if (normalized.includes('breakthrough') || normalized.includes('insight') || normalized.includes('discover')) {
371
+ return 'breakthrough'
372
+ }
373
+ if (normalized.includes('unresolved') || normalized.includes('todo') || normalized.includes('open') || normalized.includes('pending')) {
374
+ return 'unresolved'
375
+ }
376
+ if (normalized.includes('state') || normalized.includes('status') || normalized.includes('current')) {
377
+ return 'state'
378
+ }
379
+
380
+ // Default fallback
381
+ return 'technical'
382
+ }
383
+
384
+ /**
385
+ * Get emoji for a canonical context type
386
+ */
387
+ export function getV3Emoji(contextType: CanonicalContextType | string): string {
388
+ const canonical = CANONICAL_CONTEXT_TYPES.includes(contextType as CanonicalContextType)
389
+ ? contextType as CanonicalContextType
390
+ : migrateContextType(contextType)
391
+ return V3_EMOJI_MAP[canonical]
392
+ }