@machinespirits/eval 0.1.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.
Files changed (68) hide show
  1. package/components/MobileEvalDashboard.tsx +267 -0
  2. package/components/comparison/DeltaAnalysisTable.tsx +137 -0
  3. package/components/comparison/ProfileComparisonCard.tsx +176 -0
  4. package/components/comparison/RecognitionABMode.tsx +385 -0
  5. package/components/comparison/RecognitionMetricsPanel.tsx +135 -0
  6. package/components/comparison/WinnerIndicator.tsx +64 -0
  7. package/components/comparison/index.ts +5 -0
  8. package/components/mobile/BottomSheet.tsx +233 -0
  9. package/components/mobile/DimensionBreakdown.tsx +210 -0
  10. package/components/mobile/DocsView.tsx +363 -0
  11. package/components/mobile/LogsView.tsx +481 -0
  12. package/components/mobile/PsychodynamicQuadrant.tsx +261 -0
  13. package/components/mobile/QuickTestView.tsx +1098 -0
  14. package/components/mobile/RecognitionTypeChart.tsx +124 -0
  15. package/components/mobile/RecognitionView.tsx +809 -0
  16. package/components/mobile/RunDetailView.tsx +261 -0
  17. package/components/mobile/RunHistoryView.tsx +367 -0
  18. package/components/mobile/ScoreRadial.tsx +211 -0
  19. package/components/mobile/StreamingLogPanel.tsx +230 -0
  20. package/components/mobile/SynthesisStrategyChart.tsx +140 -0
  21. package/config/interaction-eval-scenarios.yaml +832 -0
  22. package/config/learner-agents.yaml +248 -0
  23. package/docs/research/ABLATION-DIALOGUE-ROUNDS.md +52 -0
  24. package/docs/research/ABLATION-MODEL-SELECTION.md +53 -0
  25. package/docs/research/ADVANCED-EVAL-ANALYSIS.md +60 -0
  26. package/docs/research/ANOVA-RESULTS-2026-01-14.md +257 -0
  27. package/docs/research/COMPREHENSIVE-EVALUATION-PLAN.md +586 -0
  28. package/docs/research/COST-ANALYSIS.md +56 -0
  29. package/docs/research/CRITICAL-REVIEW-RECOGNITION-TUTORING.md +340 -0
  30. package/docs/research/DYNAMIC-VS-SCRIPTED-ANALYSIS.md +291 -0
  31. package/docs/research/EVAL-SYSTEM-ANALYSIS.md +306 -0
  32. package/docs/research/FACTORIAL-RESULTS-2026-01-14.md +301 -0
  33. package/docs/research/IMPLEMENTATION-PLAN-CRITIQUE-RESPONSE.md +1988 -0
  34. package/docs/research/LONGITUDINAL-DYADIC-EVALUATION.md +282 -0
  35. package/docs/research/MULTI-JUDGE-VALIDATION-2026-01-14.md +147 -0
  36. package/docs/research/PAPER-EXTENSION-DYADIC.md +204 -0
  37. package/docs/research/PAPER-UNIFIED.md +659 -0
  38. package/docs/research/PAPER-UNIFIED.pdf +0 -0
  39. package/docs/research/PROMPT-IMPROVEMENTS-2026-01-14.md +356 -0
  40. package/docs/research/SESSION-NOTES-2026-01-11-RECOGNITION-EVAL.md +419 -0
  41. package/docs/research/apa.csl +2133 -0
  42. package/docs/research/archive/PAPER-DRAFT-RECOGNITION-TUTORING.md +1637 -0
  43. package/docs/research/archive/paper-multiagent-tutor.tex +978 -0
  44. package/docs/research/paper-draft/full-paper.md +136 -0
  45. package/docs/research/paper-draft/images/pasted-image-2026-01-24T03-47-47-846Z-d76a7ae2.png +0 -0
  46. package/docs/research/paper-draft/references.bib +515 -0
  47. package/docs/research/transcript-baseline.md +139 -0
  48. package/docs/research/transcript-recognition-multiagent.md +187 -0
  49. package/hooks/useEvalData.ts +625 -0
  50. package/index.js +27 -0
  51. package/package.json +73 -0
  52. package/routes/evalRoutes.js +3002 -0
  53. package/scripts/advanced-eval-analysis.js +351 -0
  54. package/scripts/analyze-eval-costs.js +378 -0
  55. package/scripts/analyze-eval-results.js +513 -0
  56. package/scripts/analyze-interaction-evals.js +368 -0
  57. package/server-init.js +45 -0
  58. package/server.js +162 -0
  59. package/services/benchmarkService.js +1892 -0
  60. package/services/evaluationRunner.js +739 -0
  61. package/services/evaluationStore.js +1121 -0
  62. package/services/learnerConfigLoader.js +385 -0
  63. package/services/learnerTutorInteractionEngine.js +857 -0
  64. package/services/memory/learnerMemoryService.js +1227 -0
  65. package/services/memory/learnerWritingPad.js +577 -0
  66. package/services/memory/tutorWritingPad.js +674 -0
  67. package/services/promptRecommendationService.js +493 -0
  68. package/services/rubricEvaluator.js +826 -0
@@ -0,0 +1,577 @@
1
+ /**
2
+ * Learner Writing Pad Service
3
+ *
4
+ * Implements Freud's "Mystic Writing Pad" metaphor for learner memory:
5
+ * - Conscious: Current interaction, immediate understanding
6
+ * - Preconscious: Recent lessons, accessible with attention
7
+ * - Unconscious: Permanent traces of breakthroughs and traumas
8
+ *
9
+ * Tracks what the learner remembers, misremembers, and has forgotten.
10
+ */
11
+
12
+ import Database from 'better-sqlite3';
13
+ import path from 'path';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+ const DATA_DIR = path.join(__dirname, '..', '..', 'data');
19
+
20
+ // Initialize database
21
+ let db;
22
+ try {
23
+ db = new Database(path.join(DATA_DIR, 'learner-writing-pad.db'));
24
+
25
+ db.exec(`
26
+ CREATE TABLE IF NOT EXISTS conscious_layer (
27
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
28
+ learner_id TEXT NOT NULL,
29
+ session_id TEXT NOT NULL,
30
+ current_topic TEXT,
31
+ current_understanding TEXT, -- 'none', 'partial', 'solid', 'transforming'
32
+ active_questions TEXT, -- JSON array
33
+ emotional_state TEXT, -- 'curious', 'frustrated', 'engaged', 'confused', etc.
34
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
35
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
36
+ UNIQUE(learner_id, session_id)
37
+ );
38
+
39
+ CREATE TABLE IF NOT EXISTS preconscious_lessons (
40
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
41
+ learner_id TEXT NOT NULL,
42
+ concept TEXT NOT NULL,
43
+ initial_understanding TEXT,
44
+ current_understanding TEXT,
45
+ retention_score REAL DEFAULT 1.0, -- Decays over time
46
+ last_accessed TEXT,
47
+ access_count INTEGER DEFAULT 1,
48
+ misunderstandings TEXT, -- JSON array
49
+ connections TEXT, -- JSON array of related concepts
50
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
51
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
52
+ UNIQUE(learner_id, concept)
53
+ );
54
+
55
+ CREATE TABLE IF NOT EXISTS unconscious_breakthroughs (
56
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
57
+ learner_id TEXT NOT NULL,
58
+ moment_description TEXT NOT NULL,
59
+ concept TEXT,
60
+ impact_score REAL DEFAULT 0.5, -- How significant was this
61
+ session_number INTEGER,
62
+ context TEXT, -- What led to this breakthrough
63
+ emotional_valence TEXT, -- 'positive', 'mixed', 'neutral'
64
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
65
+ );
66
+
67
+ CREATE TABLE IF NOT EXISTS unconscious_traumas (
68
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
69
+ learner_id TEXT NOT NULL,
70
+ moment_description TEXT NOT NULL,
71
+ concept TEXT,
72
+ impact_score REAL DEFAULT 0.5, -- How significant was this
73
+ session_number INTEGER,
74
+ trigger TEXT, -- What triggered this experience
75
+ resolved INTEGER DEFAULT 0, -- Has it been worked through?
76
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
77
+ );
78
+
79
+ CREATE TABLE IF NOT EXISTS core_patterns (
80
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ learner_id TEXT NOT NULL,
82
+ pattern_type TEXT NOT NULL, -- 'learning_style', 'struggle_pattern', 'strength', etc.
83
+ pattern_key TEXT NOT NULL,
84
+ pattern_value TEXT,
85
+ confidence REAL DEFAULT 0.5,
86
+ observation_count INTEGER DEFAULT 1,
87
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
88
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
89
+ UNIQUE(learner_id, pattern_type, pattern_key)
90
+ );
91
+
92
+ CREATE TABLE IF NOT EXISTS memory_snapshots (
93
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
94
+ learner_id TEXT NOT NULL,
95
+ eval_run_id TEXT,
96
+ snapshot_data TEXT, -- JSON of full writing pad state
97
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
98
+ );
99
+
100
+ CREATE INDEX IF NOT EXISTS idx_conscious_learner ON conscious_layer(learner_id);
101
+ CREATE INDEX IF NOT EXISTS idx_preconscious_learner ON preconscious_lessons(learner_id);
102
+ CREATE INDEX IF NOT EXISTS idx_breakthroughs_learner ON unconscious_breakthroughs(learner_id);
103
+ CREATE INDEX IF NOT EXISTS idx_traumas_learner ON unconscious_traumas(learner_id);
104
+ CREATE INDEX IF NOT EXISTS idx_patterns_learner ON core_patterns(learner_id);
105
+ `);
106
+ } catch (e) {
107
+ console.warn('[LearnerWritingPad] Could not initialize database:', e.message);
108
+ }
109
+
110
+ // ============================================================================
111
+ // Conscious Layer Operations
112
+ // ============================================================================
113
+
114
+ /**
115
+ * Update the conscious layer (current interaction state)
116
+ */
117
+ export function updateConsciousLayer(learnerId, sessionId, data) {
118
+ if (!db) return null;
119
+
120
+ const stmt = db.prepare(`
121
+ INSERT INTO conscious_layer (learner_id, session_id, current_topic, current_understanding, active_questions, emotional_state, updated_at)
122
+ VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
123
+ ON CONFLICT(learner_id, session_id) DO UPDATE SET
124
+ current_topic = COALESCE(excluded.current_topic, current_topic),
125
+ current_understanding = COALESCE(excluded.current_understanding, current_understanding),
126
+ active_questions = COALESCE(excluded.active_questions, active_questions),
127
+ emotional_state = COALESCE(excluded.emotional_state, emotional_state),
128
+ updated_at = CURRENT_TIMESTAMP
129
+ `);
130
+
131
+ return stmt.run(
132
+ learnerId,
133
+ sessionId,
134
+ data.currentTopic || null,
135
+ data.currentUnderstanding || null,
136
+ data.activeQuestions ? JSON.stringify(data.activeQuestions) : null,
137
+ data.emotionalState || null
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Get current conscious state
143
+ */
144
+ export function getConsciousLayer(learnerId, sessionId) {
145
+ if (!db) return null;
146
+
147
+ const row = db.prepare(`
148
+ SELECT * FROM conscious_layer WHERE learner_id = ? AND session_id = ?
149
+ `).get(learnerId, sessionId);
150
+
151
+ if (!row) return null;
152
+
153
+ return {
154
+ currentTopic: row.current_topic,
155
+ currentUnderstanding: row.current_understanding,
156
+ activeQuestions: row.active_questions ? JSON.parse(row.active_questions) : [],
157
+ emotionalState: row.emotional_state,
158
+ updatedAt: row.updated_at,
159
+ };
160
+ }
161
+
162
+ // ============================================================================
163
+ // Preconscious Layer Operations
164
+ // ============================================================================
165
+
166
+ /**
167
+ * Record or update a learned concept
168
+ */
169
+ export function recordLesson(learnerId, concept, data) {
170
+ if (!db) return null;
171
+
172
+ const stmt = db.prepare(`
173
+ INSERT INTO preconscious_lessons (learner_id, concept, initial_understanding, current_understanding, retention_score, last_accessed, misunderstandings, connections)
174
+ VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?, ?)
175
+ ON CONFLICT(learner_id, concept) DO UPDATE SET
176
+ current_understanding = COALESCE(excluded.current_understanding, current_understanding),
177
+ retention_score = CASE
178
+ WHEN excluded.retention_score IS NOT NULL THEN excluded.retention_score
179
+ ELSE MIN(1.0, retention_score + 0.1)
180
+ END,
181
+ last_accessed = CURRENT_TIMESTAMP,
182
+ access_count = access_count + 1,
183
+ misunderstandings = COALESCE(excluded.misunderstandings, misunderstandings),
184
+ connections = COALESCE(excluded.connections, connections),
185
+ updated_at = CURRENT_TIMESTAMP
186
+ `);
187
+
188
+ return stmt.run(
189
+ learnerId,
190
+ concept,
191
+ data.initialUnderstanding || null,
192
+ data.currentUnderstanding || null,
193
+ data.retentionScore || 1.0,
194
+ data.misunderstandings ? JSON.stringify(data.misunderstandings) : null,
195
+ data.connections ? JSON.stringify(data.connections) : null
196
+ );
197
+ }
198
+
199
+ /**
200
+ * Apply memory decay to all preconscious lessons
201
+ * Called at session start to simulate forgetting
202
+ */
203
+ export function applyMemoryDecay(learnerId, decayRate = 0.05) {
204
+ if (!db) return null;
205
+
206
+ return db.prepare(`
207
+ UPDATE preconscious_lessons
208
+ SET retention_score = MAX(0.1, retention_score - ?),
209
+ updated_at = CURRENT_TIMESTAMP
210
+ WHERE learner_id = ?
211
+ `).run(decayRate, learnerId);
212
+ }
213
+
214
+ /**
215
+ * Get all preconscious lessons for a learner
216
+ */
217
+ export function getPreconsciousLessons(learnerId, minRetention = 0) {
218
+ if (!db) return [];
219
+
220
+ const rows = db.prepare(`
221
+ SELECT * FROM preconscious_lessons
222
+ WHERE learner_id = ? AND retention_score >= ?
223
+ ORDER BY retention_score DESC
224
+ `).all(learnerId, minRetention);
225
+
226
+ return rows.map(row => ({
227
+ concept: row.concept,
228
+ initialUnderstanding: row.initial_understanding,
229
+ currentUnderstanding: row.current_understanding,
230
+ retentionScore: row.retention_score,
231
+ lastAccessed: row.last_accessed,
232
+ accessCount: row.access_count,
233
+ misunderstandings: row.misunderstandings ? JSON.parse(row.misunderstandings) : [],
234
+ connections: row.connections ? JSON.parse(row.connections) : [],
235
+ }));
236
+ }
237
+
238
+ /**
239
+ * Get lessons that are at risk of being forgotten
240
+ */
241
+ export function getLessonsAtRisk(learnerId, threshold = 0.4) {
242
+ if (!db) return [];
243
+
244
+ const rows = db.prepare(`
245
+ SELECT * FROM preconscious_lessons
246
+ WHERE learner_id = ? AND retention_score < ?
247
+ ORDER BY retention_score ASC
248
+ `).all(learnerId, threshold);
249
+
250
+ return rows.map(row => ({
251
+ concept: row.concept,
252
+ retentionScore: row.retention_score,
253
+ lastAccessed: row.last_accessed,
254
+ misunderstandings: row.misunderstandings ? JSON.parse(row.misunderstandings) : [],
255
+ }));
256
+ }
257
+
258
+ // ============================================================================
259
+ // Unconscious Layer Operations
260
+ // ============================================================================
261
+
262
+ /**
263
+ * Record a breakthrough moment
264
+ */
265
+ export function recordBreakthrough(learnerId, data) {
266
+ if (!db) return null;
267
+
268
+ const stmt = db.prepare(`
269
+ INSERT INTO unconscious_breakthroughs (learner_id, moment_description, concept, impact_score, session_number, context, emotional_valence)
270
+ VALUES (?, ?, ?, ?, ?, ?, ?)
271
+ `);
272
+
273
+ return stmt.run(
274
+ learnerId,
275
+ data.momentDescription,
276
+ data.concept || null,
277
+ data.impactScore || 0.5,
278
+ data.sessionNumber || null,
279
+ data.context || null,
280
+ data.emotionalValence || 'positive'
281
+ );
282
+ }
283
+
284
+ /**
285
+ * Record a learning trauma (frustration, confusion, shame)
286
+ */
287
+ export function recordTrauma(learnerId, data) {
288
+ if (!db) return null;
289
+
290
+ const stmt = db.prepare(`
291
+ INSERT INTO unconscious_traumas (learner_id, moment_description, concept, impact_score, session_number, trigger)
292
+ VALUES (?, ?, ?, ?, ?, ?)
293
+ `);
294
+
295
+ return stmt.run(
296
+ learnerId,
297
+ data.momentDescription,
298
+ data.concept || null,
299
+ data.impactScore || 0.5,
300
+ data.sessionNumber || null,
301
+ data.trigger || null
302
+ );
303
+ }
304
+
305
+ /**
306
+ * Mark a trauma as resolved (worked through)
307
+ */
308
+ export function resolveTrauma(learnerId, traumaId) {
309
+ if (!db) return null;
310
+
311
+ return db.prepare(`
312
+ UPDATE unconscious_traumas SET resolved = 1 WHERE id = ? AND learner_id = ?
313
+ `).run(traumaId, learnerId);
314
+ }
315
+
316
+ /**
317
+ * Get breakthroughs for a learner
318
+ */
319
+ export function getBreakthroughs(learnerId) {
320
+ if (!db) return [];
321
+
322
+ const rows = db.prepare(`
323
+ SELECT * FROM unconscious_breakthroughs WHERE learner_id = ? ORDER BY impact_score DESC
324
+ `).all(learnerId);
325
+
326
+ return rows.map(row => ({
327
+ id: row.id,
328
+ momentDescription: row.moment_description,
329
+ concept: row.concept,
330
+ impactScore: row.impact_score,
331
+ sessionNumber: row.session_number,
332
+ context: row.context,
333
+ emotionalValence: row.emotional_valence,
334
+ createdAt: row.created_at,
335
+ }));
336
+ }
337
+
338
+ /**
339
+ * Get unresolved traumas for a learner
340
+ */
341
+ export function getUnresolvedTraumas(learnerId) {
342
+ if (!db) return [];
343
+
344
+ const rows = db.prepare(`
345
+ SELECT * FROM unconscious_traumas WHERE learner_id = ? AND resolved = 0 ORDER BY impact_score DESC
346
+ `).all(learnerId);
347
+
348
+ return rows.map(row => ({
349
+ id: row.id,
350
+ momentDescription: row.moment_description,
351
+ concept: row.concept,
352
+ impactScore: row.impact_score,
353
+ sessionNumber: row.session_number,
354
+ trigger: row.trigger,
355
+ createdAt: row.created_at,
356
+ }));
357
+ }
358
+
359
+ // ============================================================================
360
+ // Core Patterns Operations
361
+ // ============================================================================
362
+
363
+ /**
364
+ * Record or update a core pattern observation
365
+ */
366
+ export function recordPattern(learnerId, patternType, patternKey, patternValue, confidence = 0.5) {
367
+ if (!db) return null;
368
+
369
+ const stmt = db.prepare(`
370
+ INSERT INTO core_patterns (learner_id, pattern_type, pattern_key, pattern_value, confidence)
371
+ VALUES (?, ?, ?, ?, ?)
372
+ ON CONFLICT(learner_id, pattern_type, pattern_key) DO UPDATE SET
373
+ pattern_value = excluded.pattern_value,
374
+ confidence = MIN(1.0, confidence + 0.1),
375
+ observation_count = observation_count + 1,
376
+ updated_at = CURRENT_TIMESTAMP
377
+ `);
378
+
379
+ return stmt.run(learnerId, patternType, patternKey, patternValue, confidence);
380
+ }
381
+
382
+ /**
383
+ * Get core patterns for a learner
384
+ */
385
+ export function getCorePatterns(learnerId) {
386
+ if (!db) return {};
387
+
388
+ const rows = db.prepare(`
389
+ SELECT * FROM core_patterns WHERE learner_id = ? ORDER BY confidence DESC
390
+ `).all(learnerId);
391
+
392
+ const patterns = {};
393
+ for (const row of rows) {
394
+ if (!patterns[row.pattern_type]) {
395
+ patterns[row.pattern_type] = {};
396
+ }
397
+ patterns[row.pattern_type][row.pattern_key] = {
398
+ value: row.pattern_value,
399
+ confidence: row.confidence,
400
+ observationCount: row.observation_count,
401
+ };
402
+ }
403
+ return patterns;
404
+ }
405
+
406
+ // ============================================================================
407
+ // Full Writing Pad Operations
408
+ // ============================================================================
409
+
410
+ /**
411
+ * Get the complete writing pad state for a learner
412
+ */
413
+ export function getFullWritingPad(learnerId, sessionId = null) {
414
+ const conscious = sessionId ? getConsciousLayer(learnerId, sessionId) : null;
415
+ const preconscious = getPreconsciousLessons(learnerId);
416
+ const breakthroughs = getBreakthroughs(learnerId);
417
+ const traumas = getUnresolvedTraumas(learnerId);
418
+ const patterns = getCorePatterns(learnerId);
419
+ const atRisk = getLessonsAtRisk(learnerId);
420
+
421
+ return {
422
+ conscious,
423
+ preconscious: {
424
+ lessons: preconscious,
425
+ atRisk,
426
+ },
427
+ unconscious: {
428
+ breakthroughs,
429
+ unresolvedTraumas: traumas,
430
+ },
431
+ corePatterns: patterns,
432
+ };
433
+ }
434
+
435
+ /**
436
+ * Create a snapshot of writing pad state (for long-term tracking)
437
+ */
438
+ export function createSnapshot(learnerId, evalRunId = null) {
439
+ if (!db) return null;
440
+
441
+ const fullState = getFullWritingPad(learnerId);
442
+
443
+ const stmt = db.prepare(`
444
+ INSERT INTO memory_snapshots (learner_id, eval_run_id, snapshot_data)
445
+ VALUES (?, ?, ?)
446
+ `);
447
+
448
+ const result = stmt.run(learnerId, evalRunId, JSON.stringify(fullState));
449
+ return {
450
+ id: result.lastInsertRowid,
451
+ ...fullState,
452
+ };
453
+ }
454
+
455
+ /**
456
+ * Get snapshots for a learner (for comparing across evals)
457
+ */
458
+ export function getSnapshots(learnerId, limit = 10) {
459
+ if (!db) return [];
460
+
461
+ const rows = db.prepare(`
462
+ SELECT * FROM memory_snapshots WHERE learner_id = ?
463
+ ORDER BY created_at DESC LIMIT ?
464
+ `).all(learnerId, limit);
465
+
466
+ return rows.map(row => ({
467
+ id: row.id,
468
+ evalRunId: row.eval_run_id,
469
+ snapshotData: JSON.parse(row.snapshot_data),
470
+ createdAt: row.created_at,
471
+ }));
472
+ }
473
+
474
+ /**
475
+ * Build narrative summary of writing pad for injection into prompts
476
+ */
477
+ export function buildNarrativeSummary(learnerId, sessionId = null) {
478
+ const pad = getFullWritingPad(learnerId, sessionId);
479
+ const parts = [];
480
+
481
+ // Conscious layer
482
+ if (pad.conscious) {
483
+ parts.push(`Currently focused on: ${pad.conscious.currentTopic || 'nothing specific'}`);
484
+ if (pad.conscious.currentUnderstanding) {
485
+ parts.push(`Understanding level: ${pad.conscious.currentUnderstanding}`);
486
+ }
487
+ if (pad.conscious.activeQuestions?.length > 0) {
488
+ parts.push(`Active questions: ${pad.conscious.activeQuestions.join('; ')}`);
489
+ }
490
+ if (pad.conscious.emotionalState) {
491
+ parts.push(`Emotional state: ${pad.conscious.emotionalState}`);
492
+ }
493
+ }
494
+
495
+ // Preconscious - strong memories
496
+ const strongLessons = pad.preconscious.lessons.filter(l => l.retentionScore > 0.7);
497
+ if (strongLessons.length > 0) {
498
+ parts.push(`\nStrong understanding of: ${strongLessons.map(l => l.concept).join(', ')}`);
499
+ }
500
+
501
+ // Preconscious - at risk
502
+ if (pad.preconscious.atRisk.length > 0) {
503
+ parts.push(`Fading memories of: ${pad.preconscious.atRisk.map(l => l.concept).join(', ')}`);
504
+ }
505
+
506
+ // Unconscious - breakthroughs
507
+ const recentBreakthroughs = pad.unconscious.breakthroughs.slice(0, 3);
508
+ if (recentBreakthroughs.length > 0) {
509
+ parts.push(`\nPast breakthroughs:`);
510
+ for (const b of recentBreakthroughs) {
511
+ parts.push(` - ${b.momentDescription}`);
512
+ }
513
+ }
514
+
515
+ // Unconscious - traumas
516
+ if (pad.unconscious.unresolvedTraumas.length > 0) {
517
+ parts.push(`\nUnresolved difficulties:`);
518
+ for (const t of pad.unconscious.unresolvedTraumas) {
519
+ parts.push(` - ${t.momentDescription}${t.concept ? ` (related to ${t.concept})` : ''}`);
520
+ }
521
+ }
522
+
523
+ // Core patterns
524
+ if (Object.keys(pad.corePatterns).length > 0) {
525
+ parts.push(`\nCore patterns:`);
526
+ for (const [type, patterns] of Object.entries(pad.corePatterns)) {
527
+ const patternStrs = Object.entries(patterns)
528
+ .filter(([_, p]) => p.confidence > 0.5)
529
+ .map(([key, p]) => `${key}: ${p.value}`);
530
+ if (patternStrs.length > 0) {
531
+ parts.push(` ${type}: ${patternStrs.join(', ')}`);
532
+ }
533
+ }
534
+ }
535
+
536
+ return parts.join('\n');
537
+ }
538
+
539
+ /**
540
+ * Clear all data for a learner (for test isolation)
541
+ */
542
+ export function clearLearnerData(learnerId) {
543
+ if (!db) return;
544
+
545
+ db.prepare('DELETE FROM conscious_layer WHERE learner_id = ?').run(learnerId);
546
+ db.prepare('DELETE FROM preconscious_lessons WHERE learner_id = ?').run(learnerId);
547
+ db.prepare('DELETE FROM unconscious_breakthroughs WHERE learner_id = ?').run(learnerId);
548
+ db.prepare('DELETE FROM unconscious_traumas WHERE learner_id = ?').run(learnerId);
549
+ db.prepare('DELETE FROM core_patterns WHERE learner_id = ?').run(learnerId);
550
+ db.prepare('DELETE FROM memory_snapshots WHERE learner_id = ?').run(learnerId);
551
+ }
552
+
553
+ export default {
554
+ // Conscious
555
+ updateConsciousLayer,
556
+ getConsciousLayer,
557
+ // Preconscious
558
+ recordLesson,
559
+ applyMemoryDecay,
560
+ getPreconsciousLessons,
561
+ getLessonsAtRisk,
562
+ // Unconscious
563
+ recordBreakthrough,
564
+ recordTrauma,
565
+ resolveTrauma,
566
+ getBreakthroughs,
567
+ getUnresolvedTraumas,
568
+ // Patterns
569
+ recordPattern,
570
+ getCorePatterns,
571
+ // Full pad
572
+ getFullWritingPad,
573
+ createSnapshot,
574
+ getSnapshots,
575
+ buildNarrativeSummary,
576
+ clearLearnerData,
577
+ };