claude-mem 3.2.0 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/claude-mem +0 -0
  2. package/package.json +1 -2
  3. package/dist/bin/cli.d.ts +0 -2
  4. package/dist/bin/cli.js +0 -129
  5. package/dist/commands/compress.d.ts +0 -2
  6. package/dist/commands/compress.js +0 -27
  7. package/dist/commands/hooks.d.ts +0 -19
  8. package/dist/commands/hooks.js +0 -131
  9. package/dist/commands/install.d.ts +0 -2
  10. package/dist/commands/install.js +0 -649
  11. package/dist/commands/load-context.d.ts +0 -2
  12. package/dist/commands/load-context.js +0 -108
  13. package/dist/commands/logs.d.ts +0 -2
  14. package/dist/commands/logs.js +0 -76
  15. package/dist/commands/migrate-to-jsonl.d.ts +0 -5
  16. package/dist/commands/migrate-to-jsonl.js +0 -99
  17. package/dist/commands/status.d.ts +0 -1
  18. package/dist/commands/status.js +0 -136
  19. package/dist/commands/uninstall.d.ts +0 -2
  20. package/dist/commands/uninstall.js +0 -107
  21. package/dist/constants.d.ts +0 -271
  22. package/dist/constants.js +0 -199
  23. package/dist/core/compression/TranscriptCompressor.d.ts +0 -83
  24. package/dist/core/compression/TranscriptCompressor.js +0 -602
  25. package/dist/core/orchestration/PromptOrchestrator.d.ts +0 -165
  26. package/dist/core/orchestration/PromptOrchestrator.js +0 -182
  27. package/dist/lib/time-utils.d.ts +0 -5
  28. package/dist/lib/time-utils.js +0 -70
  29. package/dist/prompts/constants.d.ts +0 -126
  30. package/dist/prompts/constants.js +0 -161
  31. package/dist/prompts/index.d.ts +0 -10
  32. package/dist/prompts/index.js +0 -11
  33. package/dist/prompts/templates/analysis/AnalysisTemplates.d.ts +0 -13
  34. package/dist/prompts/templates/analysis/AnalysisTemplates.js +0 -94
  35. package/dist/prompts/templates/context/ContextTemplates.d.ts +0 -119
  36. package/dist/prompts/templates/context/ContextTemplates.js +0 -399
  37. package/dist/prompts/templates/hooks/HookTemplates.d.ts +0 -175
  38. package/dist/prompts/templates/hooks/HookTemplates.js +0 -394
  39. package/dist/prompts/templates/hooks/HookTemplates.test.d.ts +0 -7
  40. package/dist/prompts/templates/hooks/HookTemplates.test.js +0 -127
  41. package/dist/shared/config.d.ts +0 -4
  42. package/dist/shared/config.js +0 -41
  43. package/dist/shared/error-handler.d.ts +0 -22
  44. package/dist/shared/error-handler.js +0 -142
  45. package/dist/shared/logger.d.ts +0 -19
  46. package/dist/shared/logger.js +0 -51
  47. package/dist/shared/paths.d.ts +0 -28
  48. package/dist/shared/paths.js +0 -100
  49. package/dist/shared/types.d.ts +0 -141
  50. package/dist/shared/types.js +0 -78
@@ -1,602 +0,0 @@
1
- import { query } from '@anthropic-ai/claude-code';
2
- import fs, { createWriteStream } from 'fs';
3
- import path, { join } from 'path';
4
- import os from 'os';
5
- import { PathResolver } from '../../shared/paths.js';
6
- import { PromptOrchestrator, createAnalysisContext } from '../orchestration/PromptOrchestrator.js';
7
- import { DEBUG_MESSAGES } from '../../constants.js';
8
- import { log } from '../../shared/logger.js';
9
- /**
10
- * TranscriptCompressor handles the analysis and compression of Claude Code conversation transcripts
11
- * into a searchable memory database format using the Model Context Protocol.
12
- */
13
- export class TranscriptCompressor {
14
- paths;
15
- logStream = null;
16
- logFile = null;
17
- promptOrchestrator;
18
- // <Block> 1.1 ====================================
19
- // Constructor Initialization - Natural flow (8/10)
20
- constructor(options = {}) {
21
- this.paths = new PathResolver();
22
- this.promptOrchestrator = new PromptOrchestrator();
23
- this.ensureClaudeMemStructure();
24
- this.initializeLogging();
25
- log.debug('🤖 TranscriptCompressor initialized');
26
- }
27
- // </Block> =======================================
28
- // <Block> 1.2 ====================================
29
- // Directory Structure Validation - Natural flow (8/10)
30
- /**
31
- * Ensures that the required directory structure exists
32
- */
33
- ensureClaudeMemStructure() {
34
- const configDir = this.paths.getConfigDir();
35
- const indexDir = this.paths.getIndexDir();
36
- const archiveDir = this.paths.getArchiveDir();
37
- const logsDir = this.paths.getLogsDir();
38
- PathResolver.ensureDirectories([configDir, indexDir, archiveDir, logsDir]);
39
- }
40
- initializeLogging() {
41
- const logsDir = this.paths.getLogsDir();
42
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
43
- this.logFile = join(logsDir, `claude-mem-${timestamp}.log`);
44
- this.logStream = createWriteStream(this.logFile, { flags: 'a' });
45
- this.debugLog('🚀 DEBUG LOG STARTED');
46
- this.debugLog(`📁 Log file: ${this.logFile}`);
47
- this.debugLog('═'.repeat(60));
48
- }
49
- debugLog(message) {
50
- if (!this.logStream)
51
- return;
52
- const timestamp = new Date().toISOString();
53
- const logLine = `[${timestamp}] ${message}\n`;
54
- this.logStream.write(logLine);
55
- }
56
- closeLogging() {
57
- if (this.logStream) {
58
- this.debugLog('✅ DEBUG LOG ENDED');
59
- this.logStream.end();
60
- }
61
- }
62
- // </Block> =======================================
63
- // <Block> 1.3 ====================================
64
- // </Block> =======================================
65
- // <Block> 1.4 ====================================
66
- // Main Compression Flow - DEBUG GUARDS INTERRUPT FLOW (5/10)
67
- /**
68
- * Main compression method that processes a transcript and creates compressed memories
69
- */
70
- async compress(transcriptPath, sessionId) {
71
- this.debugLog(`🚀 Starting compression for: ${transcriptPath}`);
72
- this.debugLog(`📋 Session ID: ${sessionId || 'auto-generated'}`);
73
- try {
74
- const projectPrefix = PathResolver.getCurrentProjectPrefix();
75
- log.debug(DEBUG_MESSAGES.PROJECT_NAME(projectPrefix));
76
- this.debugLog(`📝 PROJECT PREFIX: ${projectPrefix}`);
77
- // Read and parse transcript
78
- const content = fs.readFileSync(transcriptPath, 'utf-8');
79
- this.debugLog(`📖 Reading transcript: ${content.length} bytes`);
80
- const lines = content.trim().split('\n').filter(line => line.trim());
81
- const messages = [];
82
- let parseErrors = 0;
83
- for (let i = 0; i < lines.length; i++) {
84
- try {
85
- const parsed = JSON.parse(lines[i]);
86
- messages.push(parsed);
87
- }
88
- catch (e) {
89
- parseErrors++;
90
- log.debug(`Parse error on line ${i + 1}: ${e.message}`);
91
- }
92
- }
93
- log.debug(DEBUG_MESSAGES.TRANSCRIPT_STATS(content.length, messages.length));
94
- if (parseErrors > 0) {
95
- log.debug(`Parse errors: ${parseErrors}`);
96
- }
97
- this.debugLog(`📊 Transcript loaded: ${lines.length} lines, ${messages.length} messages, ${parseErrors} parse errors`);
98
- // Generate final session ID
99
- const finalSessionId = sessionId || path.basename(transcriptPath, '.jsonl');
100
- // Get timestamp from first message or use current time
101
- const firstMessage = messages.find(m => m.timestamp);
102
- let timestamp = new Date().toISOString();
103
- if (firstMessage?.timestamp) {
104
- const ts = Number(firstMessage.timestamp);
105
- // Check if timestamp is in seconds (Unix) or milliseconds
106
- // Unix timestamps are typically 10 digits, JS timestamps are 13
107
- const dateValue = ts < 10000000000 ? ts * 1000 : ts;
108
- try {
109
- timestamp = new Date(dateValue).toISOString();
110
- }
111
- catch (e) {
112
- // Fall back to current time if timestamp is invalid
113
- this.debugLog(`⚠️ Invalid timestamp: ${firstMessage.timestamp}, using current time`);
114
- }
115
- }
116
- // Archive filename for reference
117
- const archiveFilename = `${finalSessionId}.jsonl.archive`;
118
- // Format conversation for analysis
119
- const conversationText = this.formatConversationForPrompt(messages);
120
- // Create analysis prompt using PromptOrchestrator
121
- const analysisContext = createAnalysisContext(conversationText, finalSessionId, {
122
- projectName: projectPrefix,
123
- trigger: 'manual'
124
- });
125
- const analysisPrompt = this.promptOrchestrator.createAnalysisPrompt(analysisContext);
126
- log.debug('📤 Analysis prompt created');
127
- log.debug(`📊 Prompt length: ${analysisPrompt.prompt.length} characters`);
128
- // LOG THE FULL PROMPT TO DEBUG FILE
129
- const promptDebugPath = path.join(this.paths.getLogsDir(), `claude-prompt-${Date.now()}.txt`);
130
- fs.writeFileSync(promptDebugPath, `=== CLAUDE ANALYSIS PROMPT ===\n${analysisPrompt.prompt}\n`);
131
- this.debugLog(`📝 Full prompt saved to: ${promptDebugPath}`);
132
- // Find Claude Code executable and MCP config
133
- const claudePath = this.findClaudeExecutable();
134
- const mcpConfigPath = this.findMCPConfig();
135
- if (claudePath) {
136
- log.debug(DEBUG_MESSAGES.CLAUDE_PATH_FOUND(claudePath));
137
- }
138
- if (mcpConfigPath) {
139
- log.debug(DEBUG_MESSAGES.MCP_CONFIG_USED(mcpConfigPath));
140
- }
141
- // Call Claude SDK for analysis
142
- this.debugLog('🤖 Calling Claude SDK with MCP tools...');
143
- const response = await query({
144
- prompt: analysisPrompt.prompt,
145
- options: {
146
- allowedTools: [
147
- 'mcp__claude-mem__chroma_list_collections',
148
- 'mcp__claude-mem__chroma_create_collection',
149
- 'mcp__claude-mem__chroma_peek_collection',
150
- 'mcp__claude-mem__chroma_get_collection_info',
151
- 'mcp__claude-mem__chroma_get_collection_count',
152
- 'mcp__claude-mem__chroma_modify_collection',
153
- 'mcp__claude-mem__chroma_fork_collection',
154
- 'mcp__claude-mem__chroma_delete_collection',
155
- 'mcp__claude-mem__chroma_add_documents',
156
- 'mcp__claude-mem__chroma_query_documents',
157
- 'mcp__claude-mem__chroma_get_documents',
158
- 'mcp__claude-mem__chroma_update_documents',
159
- 'mcp__claude-mem__chroma_delete_documents',
160
- ],
161
- pathToClaudeCodeExecutable: '/Users/alexnewman/.nvm/versions/node/v24.5.0/bin/claude',
162
- },
163
- });
164
- this.debugLog('✅ Claude SDK response received');
165
- // Process response and extract summaries from JSON
166
- this.debugLog('🔄 Processing Claude JSON response...');
167
- const extractResult = await this.processClaudeResponse(response);
168
- this.debugLog(`📋 Extracted ${extractResult.summaries.length} summaries from JSON`);
169
- if (extractResult.overview) {
170
- this.debugLog(`📝 Overview: ${extractResult.overview}`);
171
- }
172
- log.debug(DEBUG_MESSAGES.COMPRESSION_COMPLETE(extractResult.summaries.length));
173
- // Continue processing even with zero summaries - let the natural flow handle empty results
174
- // Create archive and update index
175
- const archivePath = this.createArchive(transcriptPath, projectPrefix, finalSessionId, content);
176
- this.debugLog(`📦 Archive created: ${archivePath}`);
177
- this.appendToIndex(extractResult.summaries, extractResult.overview, projectPrefix, finalSessionId, messages, archivePath);
178
- this.debugLog(`📥 Written ${extractResult.summaries.length} summaries to index`);
179
- log.debug(`✅ SUCCESS`);
180
- log.debug(`Archive created: ${archivePath}`);
181
- log.debug(`Summaries created: ${extractResult.summaries.length}`);
182
- this.debugLog('✅ Compression completed successfully');
183
- this.closeLogging();
184
- return archivePath;
185
- }
186
- catch (error) {
187
- log.error('COMPRESSION FAILED', error, {
188
- transcriptPath,
189
- sessionId
190
- });
191
- this.debugLog(`❌ ERROR: ${error instanceof Error ? error.message : String(error)}`);
192
- this.closeLogging();
193
- throw error;
194
- }
195
- }
196
- // </Block> =======================================
197
- /**
198
- * Finds Claude Code executable in common locations
199
- */
200
- findClaudeExecutable() {
201
- const possiblePaths = [
202
- '/opt/homebrew/bin/claude',
203
- '/usr/local/bin/claude',
204
- process.env.CLAUDE_CODE_PATH,
205
- ].filter(Boolean);
206
- for (const path of possiblePaths) {
207
- if (path && fs.existsSync(path)) {
208
- return path;
209
- }
210
- }
211
- return undefined;
212
- }
213
- /**
214
- * Finds MCP configuration file
215
- */
216
- findMCPConfig() {
217
- const possibleConfigs = [
218
- join(process.cwd(), '.mcp.json'),
219
- join(os.homedir(), '.claude.json'),
220
- join(os.homedir(), '.claude', '.mcp.json'),
221
- ];
222
- const mcpConfigPath = possibleConfigs.find(fs.existsSync);
223
- return mcpConfigPath || join(os.homedir(), '.claude.json');
224
- }
225
- // <Block> 1.5 ====================================
226
- // Claude Response Processing - JSON extraction with pipe-separated output (9/10)
227
- /**
228
- * Processes Claude response to extract summaries from JSON
229
- */
230
- async processClaudeResponse(response) {
231
- let fullContent = '';
232
- // Extract content using polymorphic handlers
233
- fullContent = await this.extractResponseContent(response, []);
234
- // DEBUG: Log the full content to see what Claude is returning
235
- this.debugLog(`🔍 Claude response content length: ${fullContent.length}`);
236
- // Write raw response to debug file for troubleshooting
237
- const debugPath = path.join(this.paths.getLogsDir(), `claude-response-${Date.now()}.txt`);
238
- fs.writeFileSync(debugPath, `=== CLAUDE RAW RESPONSE ===\n${fullContent}\n`);
239
- this.debugLog(`📝 Raw response saved to: ${debugPath}`);
240
- // Extract JSON from response tags
241
- const extractResult = this.extractJSONResponse(fullContent);
242
- this.debugLog(`📊 Extracted ${extractResult.summaries.length} summaries from JSON`);
243
- if (extractResult.summaries.length === 0) {
244
- this.debugLog(`⚠️ No summaries found in JSON response`);
245
- }
246
- return extractResult;
247
- }
248
- /**
249
- * Extracts content from response
250
- */
251
- async extractResponseContent(response, summaries) {
252
- // Handle streaming response
253
- if (response && typeof response === 'object' && Symbol.asyncIterator in response) {
254
- let content = '';
255
- for await (const message of response) {
256
- content += this.extractMessageContent(message);
257
- if (message?.type === 'result' && message?.result) {
258
- content = message.result;
259
- }
260
- }
261
- return content;
262
- }
263
- // Handle string response
264
- if (typeof response === 'string') {
265
- return response;
266
- }
267
- // Handle array response
268
- if (Array.isArray(response)) {
269
- return response.map(item => {
270
- if (typeof item === 'string')
271
- return item;
272
- if (item?.text)
273
- return item.text;
274
- if (item?.content)
275
- return item.content;
276
- return '';
277
- }).filter(Boolean).join('\n');
278
- }
279
- // Handle object response
280
- if (typeof response === 'object' && response !== null) {
281
- if (response?.text)
282
- return response.text;
283
- if (response?.content)
284
- return response.content;
285
- if (response?.message)
286
- return response.message;
287
- return '';
288
- }
289
- return '';
290
- }
291
- /**
292
- * Extracts content from a single message
293
- */
294
- extractMessageContent(message) {
295
- let content = '';
296
- if (message?.content)
297
- content += message.content;
298
- if (message?.text)
299
- content += message.text;
300
- if (message?.data)
301
- content += message.data;
302
- if (message?.message?.content && Array.isArray(message.message.content)) {
303
- message.message.content.forEach((item) => {
304
- if (item.type === 'text' && item.text) {
305
- content += item.text;
306
- }
307
- });
308
- }
309
- return content;
310
- }
311
- /**
312
- * Extracts JSON response and returns raw JSON objects
313
- */
314
- extractJSONResponse(content) {
315
- try {
316
- // Extract JSON from response tags
317
- const jsonMatch = content.match(/<JSONResponse>([\s\S]*?)<\/JSONResponse>/);
318
- if (!jsonMatch) {
319
- this.debugLog(`⚠️ No <JSONResponse> tags found in response`);
320
- return { overview: null, summaries: [] };
321
- }
322
- const jsonContent = jsonMatch[1].trim();
323
- this.debugLog(`✅ Found JSON response: ${jsonContent.length} chars`);
324
- // Parse the JSON
325
- const parsed = JSON.parse(jsonContent);
326
- if (!parsed.summaries || !Array.isArray(parsed.summaries)) {
327
- this.debugLog(`⚠️ Invalid JSON structure: missing summaries array`);
328
- return { overview: null, summaries: [] };
329
- }
330
- // Return raw JSON objects instead of converting to pipe-separated format
331
- const validSummaries = [];
332
- parsed.summaries.forEach((summary, index) => {
333
- if (!summary.text || !summary.document_id) {
334
- this.debugLog(`⚠️ Skipping invalid summary at index ${index}`);
335
- return;
336
- }
337
- // Ensure required fields are present
338
- const validSummary = {
339
- text: summary.text,
340
- document_id: summary.document_id,
341
- keywords: summary.keywords || '',
342
- timestamp: summary.timestamp || new Date().toISOString(),
343
- archive: summary.archive || `${summary.document_id}.jsonl.archive`
344
- };
345
- validSummaries.push(validSummary);
346
- this.debugLog(`✅ Valid summary ${index + 1}: ${summary.document_id}`);
347
- });
348
- // Store overview if present
349
- if (parsed.overview) {
350
- this.debugLog(`📝 Session overview: ${parsed.overview}`);
351
- }
352
- return { overview: parsed.overview || null, summaries: validSummaries };
353
- }
354
- catch (error) {
355
- this.debugLog(`❌ Failed to parse JSON response: ${error}`);
356
- // Fallback: try to extract any pipe-separated lines that might exist
357
- this.debugLog(`🔄 Attempting fallback to pipe-separated format...`);
358
- const legacyLines = this.extractLegacyPipeSeparatedLines(content);
359
- // Convert legacy lines to JSON format for consistency
360
- const legacySummaries = legacyLines.map((line, index) => {
361
- const parts = line.split(' | ');
362
- return {
363
- text: parts[0] || '',
364
- document_id: parts[1] || `legacy_${Date.now()}_${index}`,
365
- keywords: parts[2] || '',
366
- timestamp: parts[3] || new Date().toISOString(),
367
- archive: parts[4] || `legacy_${Date.now()}_${index}.jsonl.archive`
368
- };
369
- });
370
- return { overview: null, summaries: legacySummaries };
371
- }
372
- }
373
- /**
374
- * Legacy fallback for pipe-separated format
375
- */
376
- extractLegacyPipeSeparatedLines(content) {
377
- const lines = content.split('\n');
378
- const pipeLines = [];
379
- lines.forEach((line) => {
380
- const trimmed = line.trim();
381
- if (trimmed && trimmed.includes(' | ') && trimmed.split(' | ').length >= 3) {
382
- pipeLines.push(trimmed);
383
- }
384
- });
385
- this.debugLog(`📊 Fallback extracted ${pipeLines.length} pipe-separated lines`);
386
- return pipeLines;
387
- }
388
- // </Block> =======================================
389
- // <Block> 1.7 ====================================
390
- // Conversation Formatting - LONG BUT MOSTLY NATURAL (6/10)
391
- /**
392
- * Formats conversation messages for analysis prompt
393
- */
394
- formatConversationForPrompt(messages) {
395
- const pipeLines = [];
396
- messages.forEach((m, index) => {
397
- const role = m.type === 'assistant' ? 'assistant'
398
- : m.type === 'user' ? 'user'
399
- : (m.type === 'result' || m.type === 'system' || m.type === 'summary') ? 'system'
400
- : m.message?.role || m.role;
401
- const content = this.extractContent(m);
402
- const sessionId = m.session_id || '';
403
- const timestamp = this.normalizeTimestamp(m);
404
- const messageUuid = m.uuid || '';
405
- // Escape pipe characters in content to prevent format corruption
406
- const escapedContent = content.replace(/\|/g, '\\|');
407
- // Format: content | session_id | role | timestamp | message_uuid
408
- const pipeLine = `${escapedContent} | ${sessionId} | ${role} | ${timestamp} | ${messageUuid}`;
409
- pipeLines.push(pipeLine);
410
- });
411
- log.debug(`Field filtering complete: ${pipeLines.length} messages processed`);
412
- return `<!-- TRANSCRIPT -->\n${pipeLines.join('\n')}\n<!-- /TRANSCRIPT -->`;
413
- }
414
- // </Block> =======================================
415
- // <Block> 1.6 ====================================
416
- // Message Content Extraction - Simplified (8/10)
417
- /**
418
- * Extracts content from message object
419
- */
420
- extractContent(m) {
421
- let content = '';
422
- // Extract by type
423
- if (m.type === 'assistant' || m.type === 'user') {
424
- const messageContent = m.message?.content;
425
- if (Array.isArray(messageContent)) {
426
- content = messageContent
427
- .map((item) => item.text || item.content || '')
428
- .filter(Boolean)
429
- .join(' ');
430
- }
431
- else if (messageContent) {
432
- content = String(messageContent).trim();
433
- }
434
- }
435
- else if (m.type === 'summary') {
436
- // Handle summary messages that have a different structure
437
- content = m.summary || '';
438
- }
439
- else if (m.type === 'result') {
440
- if (m.subtype === 'success' && m.result) {
441
- content = `[Result: ${m.result}]`;
442
- }
443
- else if (m.subtype === 'error_max_turns') {
444
- content = '[Error: Maximum turns reached]';
445
- }
446
- else if (m.subtype === 'error_during_execution') {
447
- content = '[Error during execution]';
448
- }
449
- }
450
- else if (m.type === 'system') {
451
- if (m.subtype === 'init') {
452
- content = `[System initialized: ${m.model}, tools: ${m.tools?.length || 0}, MCP servers: ${m.mcp_servers?.length || 0}]`;
453
- }
454
- else {
455
- // Handle other system messages
456
- content = String(m.content || '').trim();
457
- }
458
- }
459
- // Fallback to generic content extraction
460
- if (!content) {
461
- content = String(m.message?.content || m.content || '');
462
- if (Array.isArray(content)) {
463
- content = content
464
- .map((item) => item.text || item.content || '')
465
- .filter(Boolean)
466
- .join(' ');
467
- }
468
- }
469
- // Append tool use result if present
470
- if (m.toolUseResult) {
471
- const toolSummary = this.summarizeToolResult(m.toolUseResult, content);
472
- if (toolSummary) {
473
- content = content ? `${content}\n\n${toolSummary}` : toolSummary;
474
- }
475
- }
476
- return String(content).trim();
477
- }
478
- // </Block> =======================================
479
- /**
480
- * Summarizes tool use results
481
- */
482
- summarizeToolResult(toolResult, existingContent) {
483
- const summaryParts = [];
484
- if (toolResult.stdout) {
485
- const stdout = String(toolResult.stdout);
486
- if (stdout.length > 200) {
487
- const lineCount = stdout.split('\n').length;
488
- const charCount = stdout.length;
489
- const lines = stdout.split('\n');
490
- const preview = lines.slice(0, 3).join('\n');
491
- const suffix = lines.length > 6 ? `\n...\n${lines.slice(-2).join('\n')}` : '';
492
- summaryParts.push(`Result: ${preview}${suffix} (${lineCount} lines, ${charCount} chars)`);
493
- }
494
- else {
495
- summaryParts.push(`Result: ${stdout}`);
496
- }
497
- }
498
- if (toolResult.stderr && toolResult.stderr.trim()) {
499
- summaryParts.push(`Error: ${toolResult.stderr}`);
500
- }
501
- if (toolResult.interrupted) {
502
- summaryParts.push('(interrupted)');
503
- }
504
- if (toolResult.isImage) {
505
- summaryParts.push('(image output)');
506
- }
507
- return summaryParts.join('\n');
508
- }
509
- /**
510
- * Normalizes timestamp formats
511
- */
512
- normalizeTimestamp(m) {
513
- const ts = m.timestamp || m.message?.timestamp || m.created_at || m.message?.created_at;
514
- if (!ts)
515
- return '';
516
- try {
517
- const date = new Date(ts);
518
- if (isNaN(date.getTime()))
519
- return '';
520
- return date.toISOString().replace('T', ' ');
521
- }
522
- catch (e) {
523
- log.debug(`Invalid timestamp format: ${ts}`);
524
- return '';
525
- }
526
- }
527
- // <Block> 1.8 ====================================
528
- // Archive Creation - Natural flow (9/10)
529
- /**
530
- * Creates an archive file of the original transcript
531
- */
532
- createArchive(transcriptPath, projectPrefix, sessionId, content) {
533
- const projectArchiveDir = this.paths.getProjectArchiveDir(projectPrefix);
534
- PathResolver.ensureDirectory(projectArchiveDir);
535
- const archivePath = join(projectArchiveDir, `${sessionId}.jsonl.archive`);
536
- fs.writeFileSync(archivePath, content);
537
- log.debug(`📦 Created archive: ${archivePath}`);
538
- return archivePath;
539
- }
540
- // </Block> =======================================
541
- /**
542
- * Appends summaries in JSONL format to the index file
543
- * Each line is a JSON object with type field for easy parsing
544
- */
545
- appendToIndex(summaries, overview, projectPrefix, sessionId, messages, archivePath) {
546
- // Use PathResolver's getIndexPath() for consistency
547
- const indexPath = this.paths.getIndexPath();
548
- const indexDir = this.paths.getConfigDir();
549
- PathResolver.ensureDirectory(indexDir);
550
- const timestamp = new Date().toISOString();
551
- // Write session header as JSON object
552
- const sessionHeader = {
553
- type: "session",
554
- session_id: sessionId,
555
- timestamp: timestamp,
556
- project: projectPrefix
557
- };
558
- fs.appendFileSync(indexPath, JSON.stringify(sessionHeader) + '\n');
559
- // Add overview as JSON object if present
560
- if (overview) {
561
- const overviewObj = {
562
- type: "overview",
563
- content: overview,
564
- session_id: sessionId,
565
- project: projectPrefix,
566
- timestamp: timestamp
567
- };
568
- fs.appendFileSync(indexPath, JSON.stringify(overviewObj) + '\n');
569
- }
570
- // If no summaries from Claude, write diagnostic info
571
- if (!summaries || summaries.length === 0) {
572
- log.debug('📝 No summaries extracted from JSON response');
573
- const diagnosticObj = {
574
- type: "diagnostic",
575
- message: "NO SUMMARIES EXTRACTED - Check logs for valid JSON response",
576
- session_id: sessionId,
577
- project: projectPrefix,
578
- timestamp: timestamp
579
- };
580
- fs.appendFileSync(indexPath, JSON.stringify(diagnosticObj) + '\n');
581
- this.debugLog(`⚠️ No summaries for session ${sessionId} - Check if Claude returned valid JSON in <JSONResponse> tags`);
582
- }
583
- else {
584
- // Write each summary as JSONL memory object
585
- summaries.forEach((summary) => {
586
- const memoryObj = {
587
- type: "memory",
588
- text: summary.text,
589
- document_id: summary.document_id,
590
- keywords: summary.keywords,
591
- session_id: sessionId,
592
- project: projectPrefix,
593
- timestamp: summary.timestamp || timestamp,
594
- archive: path.basename(archivePath)
595
- };
596
- fs.appendFileSync(indexPath, JSON.stringify(memoryObj) + '\n');
597
- });
598
- log.debug(`📝 Appended ${summaries.length} summaries to index as JSONL`);
599
- }
600
- log.debug(`Index path: ${indexPath}`);
601
- }
602
- }