claude-memory-layer 1.0.9 → 1.0.11

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 (73) hide show
  1. package/dist/cli/index.js +1373 -184
  2. package/dist/cli/index.js.map +4 -4
  3. package/dist/core/index.js +445 -7
  4. package/dist/core/index.js.map +2 -2
  5. package/dist/hooks/post-tool-use.js +705 -43
  6. package/dist/hooks/post-tool-use.js.map +4 -4
  7. package/dist/hooks/session-end.js +593 -52
  8. package/dist/hooks/session-end.js.map +3 -3
  9. package/dist/hooks/session-start.js +581 -25
  10. package/dist/hooks/session-start.js.map +3 -3
  11. package/dist/hooks/stop.js +693 -73
  12. package/dist/hooks/stop.js.map +4 -4
  13. package/dist/hooks/user-prompt-submit.js +674 -94
  14. package/dist/hooks/user-prompt-submit.js.map +4 -4
  15. package/dist/server/api/index.js +1045 -42
  16. package/dist/server/api/index.js.map +4 -4
  17. package/dist/server/index.js +1054 -51
  18. package/dist/server/index.js.map +4 -4
  19. package/dist/services/memory-service.js +599 -25
  20. package/dist/services/memory-service.js.map +3 -3
  21. package/dist/ui/app.js +1380 -55
  22. package/dist/ui/index.html +311 -148
  23. package/dist/ui/style.css +892 -0
  24. package/docs/OPERATIONS.md +18 -0
  25. package/package.json +8 -2
  26. package/scripts/fix-sync-gap.js +32 -0
  27. package/scripts/heartbeat-memory-orchestrator.sh +28 -0
  28. package/scripts/report-sync-gap.js +26 -0
  29. package/scripts/review-queue-auto-resolve.js +21 -0
  30. package/scripts/sync-gap-auto-heal.sh +17 -0
  31. package/specs/20260207-dashboard-upgrade/context.md +38 -0
  32. package/specs/20260207-dashboard-upgrade/spec.md +96 -0
  33. package/src/cli/index.ts +110 -58
  34. package/src/core/sqlite-event-store.ts +542 -6
  35. package/src/core/sqlite-wrapper.ts +8 -0
  36. package/src/core/turn-state.ts +159 -0
  37. package/src/core/types.ts +23 -8
  38. package/src/core/vector-store.ts +21 -3
  39. package/src/hooks/post-tool-use.ts +68 -23
  40. package/src/hooks/session-end.ts +8 -3
  41. package/src/hooks/stop.ts +96 -25
  42. package/src/hooks/user-prompt-submit.ts +78 -65
  43. package/src/server/api/chat.ts +244 -0
  44. package/src/server/api/citations.ts +3 -3
  45. package/src/server/api/events.ts +30 -5
  46. package/src/server/api/index.ts +7 -1
  47. package/src/server/api/projects.ts +74 -0
  48. package/src/server/api/search.ts +3 -3
  49. package/src/server/api/sessions.ts +3 -3
  50. package/src/server/api/stats.ts +43 -7
  51. package/src/server/api/turns.ts +143 -0
  52. package/src/server/api/utils.ts +46 -0
  53. package/src/services/memory-service.ts +208 -9
  54. package/src/services/session-history-importer.ts +215 -51
  55. package/src/ui/app.js +1380 -55
  56. package/src/ui/index.html +311 -148
  57. package/src/ui/style.css +892 -0
  58. package/.claude/settings.local.json +0 -27
  59. package/.claude-memory/test.sqlite +0 -0
  60. package/.history/package_20260201112328.json +0 -45
  61. package/.history/package_20260201113602.json +0 -45
  62. package/.history/package_20260201113713.json +0 -45
  63. package/.history/package_20260201114110.json +0 -45
  64. package/.history/package_20260201114632.json +0 -46
  65. package/.history/package_20260201133143.json +0 -45
  66. package/.history/package_20260201134319.json +0 -45
  67. package/.history/package_20260201134326.json +0 -45
  68. package/.history/package_20260201134334.json +0 -45
  69. package/.history/package_20260201134912.json +0 -45
  70. package/.history/package_20260201142928.json +0 -46
  71. package/.history/package_20260201192048.json +0 -47
  72. package/.history/package_20260202114053.json +0 -49
  73. package/test_access.js +0 -49
@@ -0,0 +1,18 @@
1
+ # Operations Runbook
2
+
3
+ ## Health scripts
4
+
5
+ - `npm run ops:sync-gap:report`
6
+ - `npm run ops:sync-gap:fix`
7
+ - `npm run ops:sync-gap:heal`
8
+ - `npm run ops:review:resolve`
9
+ - `npm run ops:heartbeat`
10
+
11
+ ## Status policy
12
+
13
+ - `ok`: no failed outbox items and no un-leveled events after heal
14
+ - `needs-attention`: failed outbox remains or level sync still broken
15
+
16
+ ## Notes
17
+
18
+ These scripts are best-effort operational automation for Claude memory DB (`~/.claude-code/memory/events.sqlite`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-memory-layer",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Claude Code plugin that learns from conversations to provide personalized assistance",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -13,7 +13,13 @@
13
13
  "test": "vitest",
14
14
  "test:coverage": "vitest --coverage",
15
15
  "lint": "eslint src/**/*.ts",
16
- "typecheck": "tsc --noEmit"
16
+ "typecheck": "tsc --noEmit",
17
+ "ops:sync-gap:report": "node scripts/report-sync-gap.js",
18
+ "ops:sync-gap:fix": "node scripts/fix-sync-gap.js",
19
+ "ops:sync-gap:heal": "bash scripts/sync-gap-auto-heal.sh",
20
+ "ops:review:resolve": "node scripts/review-queue-auto-resolve.js",
21
+ "ops:heartbeat": "bash scripts/heartbeat-memory-orchestrator.sh",
22
+ "ops:health": "npm run ops:sync-gap:report && npm run ops:sync-gap:heal && npm run ops:review:resolve"
17
23
  },
18
24
  "keywords": [
19
25
  "claude-code",
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import Database from 'better-sqlite3';
6
+
7
+ const dbPath = process.env.CML_DB_PATH || path.join(os.homedir(), '.claude-code', 'memory', 'events.sqlite');
8
+ if (!fs.existsSync(dbPath)) {
9
+ console.log(JSON.stringify({ status: 'skip', reason: 'db_not_found', dbPath }, null, 2));
10
+ process.exit(0);
11
+ }
12
+
13
+ const db = new Database(dbPath);
14
+ const hasTable = (name) => !!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(name);
15
+
16
+ let inserted = { changes: 0 };
17
+ if (hasTable('events') && hasTable('memory_levels')) {
18
+ inserted = db.prepare(`
19
+ INSERT OR IGNORE INTO memory_levels (event_id, level, promoted_at)
20
+ SELECT e.id, 'L0', datetime('now')
21
+ FROM events e
22
+ LEFT JOIN memory_levels ml ON e.id = ml.event_id
23
+ WHERE ml.event_id IS NULL
24
+ `).run();
25
+ }
26
+
27
+ let recovered = { changes: 0 };
28
+ if (hasTable('vector_outbox')) {
29
+ recovered = db.prepare(`UPDATE vector_outbox SET status='pending', updated_at=datetime('now') WHERE status='processing'`).run();
30
+ }
31
+
32
+ console.log(JSON.stringify({ status: 'ok', dbPath, leveledInserted: Number(inserted.changes||0), recoveredProcessingOutbox: Number(recovered.changes||0) }, null, 2));
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ TMP_REPORT=$(mktemp)
5
+ TMP_HEAL=$(mktemp)
6
+ TMP_REVIEW=$(mktemp)
7
+ trap 'rm -f "$TMP_REPORT" "$TMP_HEAL" "$TMP_REVIEW"' EXIT
8
+
9
+ node scripts/report-sync-gap.js >"$TMP_REPORT" || echo '{"status":"error","reason":"report_failed"}' >"$TMP_REPORT"
10
+ bash scripts/sync-gap-auto-heal.sh >"$TMP_HEAL" || echo '{"status":"error","reason":"heal_failed"}' >"$TMP_HEAL"
11
+ node scripts/review-queue-auto-resolve.js >"$TMP_REVIEW" || echo '{"status":"error","reason":"review_failed"}' >"$TMP_REVIEW"
12
+
13
+ node - <<'EOF' "$TMP_REPORT" "$TMP_HEAL" "$TMP_REVIEW"
14
+ const fs = require('fs');
15
+ const safe = (p) => { try { return JSON.parse(fs.readFileSync(p,'utf8')); } catch { return { status:'error', reason:'parse_failed' }; } };
16
+ const [r,h,v] = process.argv.slice(2);
17
+ const report = safe(r);
18
+ const heal = safe(h);
19
+ const review = safe(v);
20
+ const after = heal.after || {};
21
+ const needsAttention = (report.outboxFailedCount||0) > 0 || (after.inEventsNotLeveledCount||0) > 0 || report.status === 'error' || heal.status === 'error';
22
+ console.log(JSON.stringify({
23
+ status: needsAttention ? 'needs-attention' : 'ok',
24
+ report,
25
+ heal,
26
+ review
27
+ }, null, 2));
28
+ EOF
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import Database from 'better-sqlite3';
6
+
7
+ const dbPath = process.env.CML_DB_PATH || path.join(os.homedir(), '.claude-code', 'memory', 'events.sqlite');
8
+ if (!fs.existsSync(dbPath)) {
9
+ console.log(JSON.stringify({ status: 'skip', reason: 'db_not_found', dbPath }, null, 2));
10
+ process.exit(0);
11
+ }
12
+
13
+ const db = new Database(dbPath, { readonly: true });
14
+ const total = db.prepare('SELECT COUNT(*) as c FROM events').get();
15
+ const noLevel = db.prepare(`SELECT COUNT(*) as c FROM events e LEFT JOIN memory_levels ml ON e.id=ml.event_id WHERE ml.event_id IS NULL`).get();
16
+ const outboxPending = db.prepare(`SELECT COUNT(*) as c FROM vector_outbox WHERE status='pending'`).get();
17
+ const outboxFailed = db.prepare(`SELECT COUNT(*) as c FROM vector_outbox WHERE status='failed'`).get();
18
+
19
+ console.log(JSON.stringify({
20
+ status: 'ok',
21
+ dbPath,
22
+ totalEvents: total.c,
23
+ inEventsNotLeveledCount: noLevel.c,
24
+ outboxPendingCount: outboxPending.c,
25
+ outboxFailedCount: outboxFailed.c,
26
+ }, null, 2));
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import Database from 'better-sqlite3';
6
+
7
+ const dbPath = process.env.CML_DB_PATH || path.join(os.homedir(), '.claude-code', 'memory', 'events.sqlite');
8
+ if (!fs.existsSync(dbPath)) {
9
+ console.log(JSON.stringify({ status: 'skip', reason: 'db_not_found', dbPath }, null, 2));
10
+ process.exit(0);
11
+ }
12
+
13
+ const db = new Database(dbPath);
14
+ const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='candidate_memories'").get();
15
+ if (!table) {
16
+ console.log(JSON.stringify({ status: 'skip', reason: 'candidate_memories_table_missing', dbPath }, null, 2));
17
+ process.exit(0);
18
+ }
19
+
20
+ const res = db.prepare(`UPDATE candidate_memories SET status='resolved', updated_at=datetime('now') WHERE status='pending' AND confidence >= 0.9`).run();
21
+ console.log(JSON.stringify({ status: 'ok', resolved: Number(res.changes || 0) }, null, 2));
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPORT=$(node scripts/report-sync-gap.js)
5
+ PENDING=$(echo "$REPORT" | node -e 'const o=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(String(o.outboxPendingCount||0));')
6
+ FAILED=$(echo "$REPORT" | node -e 'const o=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(String(o.outboxFailedCount||0));')
7
+ NOLEVEL=$(echo "$REPORT" | node -e 'const o=JSON.parse(require("fs").readFileSync(0,"utf8")); process.stdout.write(String(o.inEventsNotLeveledCount||0));')
8
+
9
+ AUTO=0
10
+ if [[ "$FAILED" -gt 0 || "$NOLEVEL" -gt 0 ]]; then
11
+ AUTO=1
12
+ node scripts/fix-sync-gap.js >/dev/null
13
+ fi
14
+
15
+ AFTER=$(node scripts/report-sync-gap.js)
16
+
17
+ node -e 'const before=JSON.parse(process.argv[1]); const after=JSON.parse(process.argv[2]); const auto=Number(process.argv[3]); console.log(JSON.stringify({status:"ok",autoFixApplied:auto,before,after},null,2));' "$REPORT" "$AFTER" "$AUTO"
@@ -0,0 +1,38 @@
1
+ # Dashboard Upgrade - Context
2
+
3
+ ## Implementation Status: COMPLETE
4
+
5
+ ## Changes Made
6
+
7
+ ### `src/ui/index.html`
8
+ - Added `data-nav` attributes to sidebar nav items (overview, knowledge-graph, memory-banks, configuration)
9
+ - Added `data-stat` attributes to stat cards (events, sessions, shared, vectors)
10
+ - Wrapped existing dashboard content in `#view-overview` page view container
11
+ - Added new page view containers: `#view-knowledge-graph`, `#view-memory-banks`, `#view-configuration`
12
+ - Added Detail Modal (`#detail-modal`) for event details
13
+ - Added List Modal (`#list-modal`) for stat card drill-downs
14
+
15
+ ### `src/ui/style.css`
16
+ - Page view system (`.page-view`, show/hide with animation)
17
+ - Modal overlay, container, header, body, close button styles
18
+ - Modal content styles (meta items, content block, context list, list items)
19
+ - Knowledge Graph view styles (grid, memory cards, topic tags)
20
+ - Memory Banks view styles (level tabs, event cards)
21
+ - Configuration view styles (grid sections, rows)
22
+ - Responsive modal styles for mobile
23
+
24
+ ### `src/ui/app.js`
25
+ - **Modal system**: `openModal()`, `closeModal()`, `closeAllModals()`, ESC key handler
26
+ - **Event detail modal**: `openDetailModal(eventId)` - fetches full content + context from `/api/events/:id`
27
+ - **Stat card handlers**:
28
+ - Total Events → `showEventsListModal()` via `/api/events?limit=50`
29
+ - Active Sessions → `showSessionsModal()` via `/api/sessions` + `showSessionDetailInModal()`
30
+ - Shared Items → `showSharedModal()` using cached state
31
+ - Vector Nodes → `showVectorsModal()` using cached state
32
+ - **Navigation**: `switchView()` toggles page views and loads content
33
+ - **Knowledge Graph view**: `loadKnowledgeGraphView()` - top topics, most accessed, most helpful
34
+ - **Memory Banks view**: `loadMemoryBanksView()` + `loadMemoryBankLevel()` - L0-L4 tabs with events
35
+ - **Configuration view**: `loadConfigurationView()` - storage, endless mode, graduation criteria
36
+
37
+ ## API Endpoints Used
38
+ All existing endpoints - no new APIs needed.
@@ -0,0 +1,96 @@
1
+ # Dashboard Upgrade Spec
2
+
3
+ ## Overview
4
+ 대시보드에 인터랙티브 기능 추가. 메모리 이벤트 클릭 시 상세 모달, stat 카드 클릭 시 관련 데이터 목록, 사이드바 네비게이션으로 페이지 전환.
5
+
6
+ ## User Stories
7
+
8
+ ### US-1: 메모리 이벤트 상세 모달
9
+ - **As a** 사용자
10
+ - **I want** 메모리 이벤트를 클릭하면 전체 내용을 모달로 볼 수 있다
11
+ - **So that** 잘린 preview가 아닌 전체 content와 context를 확인할 수 있다
12
+
13
+ ### US-2: Stat 카드 클릭 → 관련 데이터 표시
14
+ - **As a** 사용자
15
+ - **I want** Total Events, Active Sessions, Shared Items, Vector Nodes 카드를 클릭하면 해당 데이터 목록을 볼 수 있다
16
+ - **So that** 대시보드 숫자의 실제 데이터를 탐색할 수 있다
17
+
18
+ ### US-3: 사이드바 네비게이션 페이지 전환
19
+ - **As a** 사용자
20
+ - **I want** Knowledge Graph, Memory Banks, Configuration을 클릭하면 메인 콘텐츠 영역이 해당 뷰로 전환된다
21
+ - **So that** 대시보드 외의 기능에도 접근할 수 있다
22
+
23
+ ## Acceptance Criteria
24
+
25
+ ### AC-1: 메모리 이벤트 모달
26
+ - [ ] 이벤트 아이템 클릭 시 모달 오픈
27
+ - [ ] 모달에 전체 content 표시 (잘림 없이)
28
+ - [ ] eventType, timestamp, sessionId 메타데이터 표시
29
+ - [ ] context (앞뒤 이벤트) 표시
30
+ - [ ] ESC 키 또는 오버레이 클릭으로 모달 닫기
31
+ - [ ] 로딩 상태 표시
32
+
33
+ ### AC-2: Stat 카드 클릭
34
+ - [ ] **Total Events** 클릭 → 전체 이벤트 목록 모달 (타입별 필터, 페이지네이션)
35
+ - [ ] **Active Sessions** 클릭 → 세션 목록 모달 (세션 ID, 이벤트 수, 시작/종료 시간)
36
+ - [ ] **Shared Items** 클릭 → 공유 아이템 상세 모달 (Troubleshooting, Best Practices, Common Errors 카운트)
37
+ - [ ] **Vector Nodes** 클릭 → 벡터 노드 정보 모달 (벡터 수, 메모리 사용량)
38
+
39
+ ### AC-3: 사이드바 네비게이션
40
+ - [ ] **Overview** → 현재 대시보드 (기존 동작 유지)
41
+ - [ ] **Knowledge Graph** → 가장 많이 접근된 메모리, 토픽별 분포 표시
42
+ - [ ] **Memory Banks** → 레벨별 메모리 목록 (L0~L4), 졸업 기준 표시
43
+ - [ ] **Configuration** → 졸업 기준 설정, 시스템 정보 표시
44
+ - [ ] active nav-item 하이라이트 전환
45
+ - [ ] 페이지 전환 시 부드러운 트랜지션
46
+
47
+ ## Technical Constraints
48
+ - 순수 HTML/CSS/JS (프레임워크 없음)
49
+ - 기존 `app.js`, `style.css`, `index.html` 수정
50
+ - 기존 API 엔드포인트 최대한 활용
51
+ - ApexCharts는 Overview 페이지에서만 사용
52
+
53
+ ## API Endpoints (기존)
54
+
55
+ | Endpoint | Method | 용도 |
56
+ |----------|--------|------|
57
+ | `/api/events/:id` | GET | 이벤트 상세 (content + context) |
58
+ | `/api/events` | GET | 이벤트 목록 (level, sort, limit, offset) |
59
+ | `/api/sessions` | GET | 세션 목록 (page, pageSize) |
60
+ | `/api/sessions/:id` | GET | 세션 상세 |
61
+ | `/api/stats` | GET | 전체 통계 |
62
+ | `/api/stats/shared` | GET | 공유 스토어 통계 |
63
+ | `/api/stats/most-accessed` | GET | 가장 많이 접근된 메모리 |
64
+ | `/api/stats/levels/:level` | GET | 레벨별 이벤트 |
65
+ | `/api/stats/graduation` | GET | 졸업 기준 정보 |
66
+ | `/api/stats/helpfulness` | GET | 도움됨 통계 |
67
+ | `/api/stats/timeline` | GET | 활동 타임라인 |
68
+ | `/api/search?q=` | GET | 메모리 검색 |
69
+
70
+ ## UI Components (신규)
71
+
72
+ ### 1. Detail Modal (`#detail-modal`)
73
+ - overlay + centered content box
74
+ - header: 타입 배지 + 타임스탬프 + 닫기 버튼
75
+ - body: 전체 content (코드 블록 포맷팅)
76
+ - footer: context 이벤트 목록
77
+ - 애니메이션: fadeIn/fadeOut
78
+
79
+ ### 2. List Modal (`#list-modal`)
80
+ - stat 카드 클릭 시 사용하는 범용 목록 모달
81
+ - header: 제목 + 닫기 버튼
82
+ - body: 스크롤 가능한 아이템 목록
83
+ - 각 아이템 클릭 시 detail modal 열기 가능
84
+
85
+ ### 3. Page Views
86
+ - `#view-overview` - 기존 대시보드 (기본)
87
+ - `#view-knowledge-graph` - Knowledge Graph 뷰
88
+ - `#view-memory-banks` - Memory Banks 뷰
89
+ - `#view-configuration` - Configuration 뷰
90
+ - 한 번에 하나만 visible (display: none/block 전환)
91
+
92
+ ## Design Guidelines
93
+ - 기존 Deep Space 테마 유지
94
+ - 모달: `var(--bg-panel)` 배경, `var(--glass-border)` 테두리
95
+ - 애니메이션: 200~300ms ease 트랜지션
96
+ - 모바일 대응: 모달 full-width on mobile
package/src/cli/index.ts CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  getDefaultMemoryService,
14
14
  getMemoryServiceForProject
15
15
  } from '../services/memory-service.js';
16
- import { createSessionHistoryImporter } from '../services/session-history-importer.js';
16
+ import { createSessionHistoryImporter, type ProgressEvent } from '../services/session-history-importer.js';
17
17
  import { startServer, stopServer, isServerRunning } from '../server/index.js';
18
18
 
19
19
  // ============================================================
@@ -427,6 +427,81 @@ program
427
427
  }
428
428
  });
429
429
 
430
+ /**
431
+ * Render import progress to terminal
432
+ */
433
+ function renderProgress(event: ProgressEvent): void {
434
+ switch (event.phase) {
435
+ case 'scan':
436
+ console.log(` 🔍 ${event.message}`);
437
+ break;
438
+ case 'session-start': {
439
+ const pct = Math.round(((event.sessionIndex) / event.totalSessions) * 100);
440
+ const sessionName = path.basename(event.filePath, '.jsonl').slice(0, 8);
441
+ process.stdout.write(
442
+ `\r 📄 [${event.sessionIndex + 1}/${event.totalSessions}] ${pct}% | Session ${sessionName}... `
443
+ );
444
+ break;
445
+ }
446
+ case 'session-progress': {
447
+ process.stdout.write(
448
+ `\r 📄 [${event.sessionIndex + 1}/...] ${event.messagesProcessed} msgs | +${event.imported} imported, ~${event.skipped} skipped `
449
+ );
450
+ break;
451
+ }
452
+ case 'session-done': {
453
+ const imported = event.importedPrompts + event.importedResponses;
454
+ if (imported > 0) {
455
+ process.stdout.write(
456
+ `\r ✅ [${event.sessionIndex + 1}] +${event.importedPrompts} prompts, +${event.importedResponses} responses${event.skipped > 0 ? `, ~${event.skipped} skipped` : ''} \n`
457
+ );
458
+ } else if (event.skipped > 0) {
459
+ process.stdout.write(
460
+ `\r ⏭️ [${event.sessionIndex + 1}] All ${event.skipped} already imported \n`
461
+ );
462
+ } else {
463
+ process.stdout.write(
464
+ `\r ⏭️ [${event.sessionIndex + 1}] Empty session \n`
465
+ );
466
+ }
467
+ break;
468
+ }
469
+ case 'embedding':
470
+ process.stdout.write(
471
+ `\r 🧠 Embeddings: ${event.processed}/${event.total} processed `
472
+ );
473
+ if (event.processed >= event.total) {
474
+ process.stdout.write('\n');
475
+ }
476
+ break;
477
+ case 'done':
478
+ break;
479
+ }
480
+ }
481
+
482
+ function printImportSummary(result: import('../services/session-history-importer.js').ImportResult, embedCount: number): void {
483
+ console.log('\n┌─────────────────────────────────┐');
484
+ console.log('│ ✅ Import Complete │');
485
+ console.log('├─────────────────────────────────┤');
486
+ console.log(`│ Sessions processed: ${String(result.totalSessions).padStart(8)} │`);
487
+ console.log(`│ Total messages: ${String(result.totalMessages).padStart(8)} │`);
488
+ console.log(`│ Imported prompts: ${String(result.importedPrompts).padStart(8)} │`);
489
+ console.log(`│ Imported responses: ${String(result.importedResponses).padStart(8)} │`);
490
+ console.log(`│ Skipped duplicates: ${String(result.skippedDuplicates).padStart(8)} │`);
491
+ console.log(`│ Embeddings queued: ${String(embedCount).padStart(8)} │`);
492
+ console.log('└─────────────────────────────────┘');
493
+
494
+ if (result.errors.length > 0) {
495
+ console.log(`\n⚠️ Errors (${result.errors.length}):`);
496
+ for (const error of result.errors.slice(0, 5)) {
497
+ console.log(` - ${error}`);
498
+ }
499
+ if (result.errors.length > 5) {
500
+ console.log(` ... and ${result.errors.length - 5} more`);
501
+ }
502
+ }
503
+ }
504
+
430
505
  /**
431
506
  * Import command - import existing Claude Code sessions
432
507
  */
@@ -437,8 +512,11 @@ program
437
512
  .option('-s, --session <file>', 'Import specific session file (JSONL)')
438
513
  .option('-a, --all', 'Import all sessions from all projects')
439
514
  .option('-l, --limit <number>', 'Limit messages per session')
515
+ .option('-f, --force', 'Force reimport: delete existing events and reimport with turn_id grouping')
440
516
  .option('-v, --verbose', 'Show detailed progress')
441
517
  .action(async (options) => {
518
+ const startTime = Date.now();
519
+
442
520
  // Determine target project path for storage
443
521
  const targetProjectPath = options.project || process.cwd();
444
522
 
@@ -446,102 +524,76 @@ program
446
524
  const service = getMemoryServiceForProject(targetProjectPath);
447
525
  const importer = createSessionHistoryImporter(service);
448
526
 
527
+ const importOpts = {
528
+ limit: options.limit ? parseInt(options.limit) : undefined,
529
+ force: options.force,
530
+ verbose: options.verbose,
531
+ onProgress: renderProgress
532
+ };
533
+
449
534
  try {
535
+ console.log('\n⏳ Initializing memory service...');
450
536
  await service.initialize();
537
+ console.log(' ✅ Ready\n');
538
+
539
+ if (options.force) {
540
+ console.log('🔄 Force mode: existing events will be deleted and reimported with turn_id grouping\n');
541
+ }
451
542
 
452
543
  let result;
453
544
 
454
545
  if (options.session) {
455
546
  // Import specific session file
456
- console.log(`\n📥 Importing session: ${options.session}`);
457
- console.log(` Target project: ${targetProjectPath}\n`);
547
+ console.log(`📥 Importing session: ${options.session}`);
548
+ console.log(` Target: ${targetProjectPath}\n`);
458
549
  result = await importer.importSessionFile(options.session, {
550
+ ...importOpts,
459
551
  projectPath: targetProjectPath,
460
- limit: options.limit ? parseInt(options.limit) : undefined,
461
- verbose: options.verbose
462
552
  });
463
553
  } else if (options.project) {
464
554
  // Import all sessions from a project
465
- console.log(`\n📥 Importing project: ${options.project}\n`);
466
- result = await importer.importProject(options.project, {
467
- limit: options.limit ? parseInt(options.limit) : undefined,
468
- verbose: options.verbose
469
- });
555
+ console.log(`📥 Importing project: ${options.project}\n`);
556
+ result = await importer.importProject(options.project, importOpts);
470
557
  } else if (options.all) {
471
558
  // Import all sessions from all projects
472
- // Note: --all imports to global storage for backward compatibility
473
- console.log('\n📥 Importing all sessions from all projects');
559
+ console.log('📥 Importing all sessions from all projects');
474
560
  console.log(' ⚠️ Using global storage (use -p for project-specific)\n');
475
561
  const globalService = getDefaultMemoryService();
476
562
  const globalImporter = createSessionHistoryImporter(globalService);
477
563
  await globalService.initialize();
478
- result = await globalImporter.importAll({
479
- limit: options.limit ? parseInt(options.limit) : undefined,
480
- verbose: options.verbose
481
- });
564
+ result = await globalImporter.importAll(importOpts);
482
565
 
483
566
  // Process embeddings
484
- console.log('\n Processing embeddings...');
567
+ console.log('\n🧠 Processing embeddings...');
485
568
  const embedCount = await globalService.processPendingEmbeddings();
486
569
 
487
- // Show results
488
- console.log('\n✅ Import Complete\n');
489
- console.log(`Sessions processed: ${result.totalSessions}`);
490
- console.log(`Total messages: ${result.totalMessages}`);
491
- console.log(`Imported prompts: ${result.importedPrompts}`);
492
- console.log(`Imported responses: ${result.importedResponses}`);
493
- console.log(`Skipped duplicates: ${result.skippedDuplicates}`);
494
- console.log(`Embeddings processed: ${embedCount}`);
495
-
496
- if (result.errors.length > 0) {
497
- console.log(`\n⚠️ Errors (${result.errors.length}):`);
498
- for (const error of result.errors.slice(0, 5)) {
499
- console.log(` - ${error}`);
500
- }
501
- if (result.errors.length > 5) {
502
- console.log(` ... and ${result.errors.length - 5} more`);
503
- }
504
- }
570
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
571
+ printImportSummary(result, embedCount);
572
+ console.log(`\n⏱️ Completed in ${elapsed}s`);
505
573
 
506
574
  await globalService.shutdown();
507
575
  return;
508
576
  } else {
509
577
  // Default: import current project
510
578
  const cwd = process.cwd();
511
- console.log(`\n📥 Importing sessions for current project: ${cwd}\n`);
579
+ console.log(`📥 Importing sessions for: ${cwd}\n`);
512
580
  result = await importer.importProject(cwd, {
581
+ ...importOpts,
513
582
  projectPath: cwd,
514
- limit: options.limit ? parseInt(options.limit) : undefined,
515
- verbose: options.verbose
516
583
  });
517
584
  }
518
585
 
519
586
  // Process embeddings
520
- console.log('\n Processing embeddings...');
587
+ console.log('\n🧠 Processing embeddings...');
521
588
  const embedCount = await service.processPendingEmbeddings();
522
589
 
523
- // Show results
524
- console.log('\n✅ Import Complete\n');
525
- console.log(`Sessions processed: ${result.totalSessions}`);
526
- console.log(`Total messages: ${result.totalMessages}`);
527
- console.log(`Imported prompts: ${result.importedPrompts}`);
528
- console.log(`Imported responses: ${result.importedResponses}`);
529
- console.log(`Skipped duplicates: ${result.skippedDuplicates}`);
530
- console.log(`Embeddings processed: ${embedCount}`);
531
-
532
- if (result.errors.length > 0) {
533
- console.log(`\n⚠️ Errors (${result.errors.length}):`);
534
- for (const error of result.errors.slice(0, 5)) {
535
- console.log(` - ${error}`);
536
- }
537
- if (result.errors.length > 5) {
538
- console.log(` ... and ${result.errors.length - 5} more`);
539
- }
540
- }
590
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
591
+ printImportSummary(result, embedCount);
592
+ console.log(`\n⏱️ Completed in ${elapsed}s`);
541
593
 
542
594
  await service.shutdown();
543
595
  } catch (error) {
544
- console.error('Import failed:', error);
596
+ console.error('\n❌ Import failed:', error);
545
597
  process.exit(1);
546
598
  }
547
599
  });