claude-mem 3.0.2 → 3.0.4
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/.mcp.json +11 -0
- package/claude-mem +0 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +64 -0
- package/dist/commands/compress.d.ts +2 -0
- package/dist/commands/compress.js +59 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.js +372 -0
- package/dist/commands/load-context.d.ts +2 -0
- package/dist/commands/load-context.js +330 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +41 -0
- package/dist/commands/migrate.d.ts +9 -0
- package/dist/commands/migrate.js +174 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +159 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.js +105 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +33 -0
- package/dist/constants.d.ts +516 -0
- package/dist/constants.js +522 -0
- package/dist/error-handler.d.ts +17 -0
- package/dist/error-handler.js +103 -0
- package/dist/mcp-server-cli.d.ts +34 -0
- package/dist/mcp-server-cli.js +158 -0
- package/dist/mcp-server.d.ts +103 -0
- package/dist/mcp-server.js +269 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +78 -0
- package/dist/utils/HookDetector.d.ts +64 -0
- package/dist/utils/HookDetector.js +213 -0
- package/dist/utils/PathResolver.d.ts +16 -0
- package/dist/utils/PathResolver.js +55 -0
- package/dist/utils/SettingsManager.d.ts +63 -0
- package/dist/utils/SettingsManager.js +133 -0
- package/dist/utils/TranscriptCompressor.d.ts +111 -0
- package/dist/utils/TranscriptCompressor.js +486 -0
- package/dist/utils/common.d.ts +29 -0
- package/dist/utils/common.js +14 -0
- package/dist/utils/error-utils.d.ts +93 -0
- package/dist/utils/error-utils.js +238 -0
- package/dist/utils/index.d.ts +19 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.js +42 -0
- package/dist/utils/mcp-client-factory.d.ts +51 -0
- package/dist/utils/mcp-client-factory.js +115 -0
- package/dist/utils/mcp-client.d.ts +75 -0
- package/dist/utils/mcp-client.js +120 -0
- package/dist/utils/memory-mcp-client.d.ts +135 -0
- package/dist/utils/memory-mcp-client.js +490 -0
- package/dist/utils/weaviate-mcp-adapter.d.ts +102 -0
- package/dist/utils/weaviate-mcp-adapter.js +587 -0
- package/package.json +3 -2
- package/src/claude-mem.js +0 -859
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for message objects in transcript
|
|
3
|
+
*/
|
|
4
|
+
interface TranscriptMessage {
|
|
5
|
+
type: string;
|
|
6
|
+
message?: {
|
|
7
|
+
content?: string | Array<{
|
|
8
|
+
text?: string;
|
|
9
|
+
content?: string;
|
|
10
|
+
}>;
|
|
11
|
+
role?: string;
|
|
12
|
+
timestamp?: string;
|
|
13
|
+
created_at?: string;
|
|
14
|
+
};
|
|
15
|
+
content?: string | Array<{
|
|
16
|
+
text?: string;
|
|
17
|
+
content?: string;
|
|
18
|
+
}>;
|
|
19
|
+
role?: string;
|
|
20
|
+
uuid?: string;
|
|
21
|
+
session_id?: string;
|
|
22
|
+
parent_tool_use_id?: string;
|
|
23
|
+
timestamp?: string;
|
|
24
|
+
created_at?: string;
|
|
25
|
+
subtype?: string;
|
|
26
|
+
result?: string;
|
|
27
|
+
model?: string;
|
|
28
|
+
tools?: unknown[];
|
|
29
|
+
mcp_servers?: unknown[];
|
|
30
|
+
toolUseResult?: {
|
|
31
|
+
stdout?: string;
|
|
32
|
+
stderr?: string;
|
|
33
|
+
interrupted?: boolean;
|
|
34
|
+
isImage?: boolean;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Interface for tool use chains extracted from messages
|
|
39
|
+
*/
|
|
40
|
+
interface ToolUseChain {
|
|
41
|
+
id: string;
|
|
42
|
+
tools: string[];
|
|
43
|
+
messages: TranscriptMessage[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compression options for the TranscriptCompressor
|
|
47
|
+
*/
|
|
48
|
+
export interface CompressionOptions {
|
|
49
|
+
output?: string;
|
|
50
|
+
dryRun?: boolean;
|
|
51
|
+
verbose?: boolean;
|
|
52
|
+
debug?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* TranscriptCompressor handles the analysis and compression of Claude Code conversation transcripts
|
|
56
|
+
* into a searchable knowledge graph format using the Model Context Protocol.
|
|
57
|
+
*/
|
|
58
|
+
export declare class TranscriptCompressor {
|
|
59
|
+
private paths;
|
|
60
|
+
private debugMode;
|
|
61
|
+
constructor(options?: CompressionOptions);
|
|
62
|
+
/**
|
|
63
|
+
* Ensures that the required directory structure exists
|
|
64
|
+
*/
|
|
65
|
+
private ensureClaudeMemStructure;
|
|
66
|
+
/**
|
|
67
|
+
* Extracts tool use chains from conversation messages
|
|
68
|
+
*/
|
|
69
|
+
extractToolUseChains(messages: TranscriptMessage[]): ToolUseChain[];
|
|
70
|
+
/**
|
|
71
|
+
* Main compression method that processes a transcript and creates compressed memories
|
|
72
|
+
*/
|
|
73
|
+
compress(transcriptPath: string, sessionId?: string): Promise<string>;
|
|
74
|
+
/**
|
|
75
|
+
* Finds Claude Code executable in common locations
|
|
76
|
+
*/
|
|
77
|
+
private findClaudeExecutable;
|
|
78
|
+
/**
|
|
79
|
+
* Finds MCP configuration file
|
|
80
|
+
*/
|
|
81
|
+
private findMCPConfig;
|
|
82
|
+
/**
|
|
83
|
+
* Processes Claude response to extract summaries
|
|
84
|
+
*/
|
|
85
|
+
private processClaudeResponse;
|
|
86
|
+
/**
|
|
87
|
+
* Formats conversation messages for analysis prompt
|
|
88
|
+
*/
|
|
89
|
+
private formatConversationForPrompt;
|
|
90
|
+
/**
|
|
91
|
+
* Extracts content from message object
|
|
92
|
+
*/
|
|
93
|
+
private extractContent;
|
|
94
|
+
/**
|
|
95
|
+
* Summarizes tool use results
|
|
96
|
+
*/
|
|
97
|
+
private summarizeToolResult;
|
|
98
|
+
/**
|
|
99
|
+
* Normalizes timestamp formats
|
|
100
|
+
*/
|
|
101
|
+
private normalizeTimestamp;
|
|
102
|
+
/**
|
|
103
|
+
* Creates an archive file of the original transcript
|
|
104
|
+
*/
|
|
105
|
+
private createArchive;
|
|
106
|
+
/**
|
|
107
|
+
* Appends compression summaries to the index file
|
|
108
|
+
*/
|
|
109
|
+
private appendToIndex;
|
|
110
|
+
}
|
|
111
|
+
export {};
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import { query } from '@anthropic-ai/claude-code';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path, { join } from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { PathResolver } from './PathResolver.js';
|
|
6
|
+
import { createAnalysisPrompt, DEBUG_MESSAGES } from '../constants.js';
|
|
7
|
+
import { log } from './logger.js';
|
|
8
|
+
import { CompressionError } from '../types.js';
|
|
9
|
+
/**
|
|
10
|
+
* TranscriptCompressor handles the analysis and compression of Claude Code conversation transcripts
|
|
11
|
+
* into a searchable knowledge graph format using the Model Context Protocol.
|
|
12
|
+
*/
|
|
13
|
+
export class TranscriptCompressor {
|
|
14
|
+
paths;
|
|
15
|
+
debugMode;
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.paths = new PathResolver();
|
|
18
|
+
this.debugMode = options.debug || false;
|
|
19
|
+
this.ensureClaudeMemStructure();
|
|
20
|
+
if (this.debugMode) {
|
|
21
|
+
log.debug('🤖 TranscriptCompressor initialized');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ensures that the required directory structure exists
|
|
26
|
+
*/
|
|
27
|
+
ensureClaudeMemStructure() {
|
|
28
|
+
const configDir = this.paths.getConfigDir();
|
|
29
|
+
const indexDir = this.paths.getIndexDir();
|
|
30
|
+
const archiveDir = this.paths.getArchiveDir();
|
|
31
|
+
const logsDir = this.paths.getLogsDir();
|
|
32
|
+
PathResolver.ensureDirectories([configDir, indexDir, archiveDir, logsDir]);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Extracts tool use chains from conversation messages
|
|
36
|
+
*/
|
|
37
|
+
extractToolUseChains(messages) {
|
|
38
|
+
const chains = [];
|
|
39
|
+
const toolUseMap = new Map();
|
|
40
|
+
messages.forEach((msg) => {
|
|
41
|
+
if (msg.parent_tool_use_id) {
|
|
42
|
+
const parentId = msg.parent_tool_use_id;
|
|
43
|
+
if (!toolUseMap.has(parentId)) {
|
|
44
|
+
toolUseMap.set(parentId, {
|
|
45
|
+
id: parentId,
|
|
46
|
+
tools: [],
|
|
47
|
+
messages: [],
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
toolUseMap.get(parentId).messages.push(msg);
|
|
51
|
+
if (msg.type === 'assistant' && msg.message?.content) {
|
|
52
|
+
const content = Array.isArray(msg.message.content)
|
|
53
|
+
? msg.message.content[0]?.text || ''
|
|
54
|
+
: msg.message.content;
|
|
55
|
+
const toolMatch = content.match(/Using (\w+) tool/i);
|
|
56
|
+
if (toolMatch) {
|
|
57
|
+
toolUseMap.get(parentId).tools.push(toolMatch[1]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
toolUseMap.forEach((chain) => {
|
|
63
|
+
if (chain.tools.length > 0) {
|
|
64
|
+
chains.push(chain);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return chains;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Main compression method that processes a transcript and creates compressed memories
|
|
71
|
+
*/
|
|
72
|
+
async compress(transcriptPath, sessionId) {
|
|
73
|
+
if (this.debugMode) {
|
|
74
|
+
log.debug(DEBUG_MESSAGES.COMPRESSION_STARTED);
|
|
75
|
+
log.debug(DEBUG_MESSAGES.TRANSCRIPT_PATH(transcriptPath));
|
|
76
|
+
log.debug(DEBUG_MESSAGES.SESSION_ID(sessionId || 'auto-detected'));
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const projectName = PathResolver.extractProjectName(transcriptPath);
|
|
80
|
+
if (this.debugMode) {
|
|
81
|
+
log.debug(DEBUG_MESSAGES.PROJECT_NAME(projectName));
|
|
82
|
+
}
|
|
83
|
+
// Read and parse transcript
|
|
84
|
+
const content = fs.readFileSync(transcriptPath, 'utf-8');
|
|
85
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
86
|
+
const messages = [];
|
|
87
|
+
let parseErrors = 0;
|
|
88
|
+
for (let i = 0; i < lines.length; i++) {
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(lines[i]);
|
|
91
|
+
messages.push(parsed);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
parseErrors++;
|
|
95
|
+
if (this.debugMode) {
|
|
96
|
+
log.debug(`Parse error on line ${i + 1}: ${e.message}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (this.debugMode) {
|
|
101
|
+
log.debug(DEBUG_MESSAGES.TRANSCRIPT_STATS(content.length, messages.length));
|
|
102
|
+
if (parseErrors > 0) {
|
|
103
|
+
log.debug(`Parse errors: ${parseErrors}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Generate final session ID
|
|
107
|
+
const finalSessionId = sessionId || path.basename(transcriptPath, '.jsonl');
|
|
108
|
+
// Format conversation for analysis
|
|
109
|
+
const conversationText = this.formatConversationForPrompt(messages);
|
|
110
|
+
const toolUseChains = this.extractToolUseChains(messages);
|
|
111
|
+
// Create analysis prompt using the utility from constants.ts
|
|
112
|
+
const prompt = createAnalysisPrompt(projectName, finalSessionId, false, // hasCompressedContent
|
|
113
|
+
'', // existingMemoriesText
|
|
114
|
+
toolUseChains);
|
|
115
|
+
if (this.debugMode) {
|
|
116
|
+
log.debug('📤 Analysis prompt created');
|
|
117
|
+
log.debug(`📊 Prompt length: ${prompt.length} characters`);
|
|
118
|
+
}
|
|
119
|
+
// Find Claude Code executable and MCP config
|
|
120
|
+
const claudePath = this.findClaudeExecutable();
|
|
121
|
+
const mcpConfigPath = this.findMCPConfig();
|
|
122
|
+
if (this.debugMode) {
|
|
123
|
+
if (claudePath) {
|
|
124
|
+
log.debug(DEBUG_MESSAGES.CLAUDE_PATH_FOUND(claudePath));
|
|
125
|
+
}
|
|
126
|
+
if (mcpConfigPath) {
|
|
127
|
+
log.debug(DEBUG_MESSAGES.MCP_CONFIG_USED(mcpConfigPath));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Call Claude SDK for analysis
|
|
131
|
+
const fullPrompt = `${prompt}\n\nConversation to compress:\n${conversationText}`;
|
|
132
|
+
const response = await query({
|
|
133
|
+
prompt: fullPrompt,
|
|
134
|
+
options: {
|
|
135
|
+
maxTurns: 1,
|
|
136
|
+
allowedTools: [
|
|
137
|
+
'mcp__claude-mem__create_entities',
|
|
138
|
+
'mcp__claude-mem__create_relations',
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
// Process response and extract summaries
|
|
143
|
+
const summaries = await this.processClaudeResponse(response);
|
|
144
|
+
if (this.debugMode) {
|
|
145
|
+
log.debug(DEBUG_MESSAGES.COMPRESSION_COMPLETE(summaries.length));
|
|
146
|
+
}
|
|
147
|
+
if (summaries.length === 0) {
|
|
148
|
+
if (this.debugMode) {
|
|
149
|
+
log.debug('⚠️ WARNING: No summaries were extracted!');
|
|
150
|
+
}
|
|
151
|
+
throw new CompressionError('No summaries extracted from Claude response', transcriptPath, 'analyzing');
|
|
152
|
+
}
|
|
153
|
+
// Create archive and update index
|
|
154
|
+
const archivePath = this.createArchive(transcriptPath, projectName, finalSessionId, content);
|
|
155
|
+
this.appendToIndex(summaries, projectName, finalSessionId, messages, archivePath);
|
|
156
|
+
if (this.debugMode) {
|
|
157
|
+
log.debug(`✅ SUCCESS`);
|
|
158
|
+
log.debug(`Archive created: ${archivePath}`);
|
|
159
|
+
log.debug(`Summaries created: ${summaries.length}`);
|
|
160
|
+
}
|
|
161
|
+
return archivePath;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
log.error('COMPRESSION FAILED', error, {
|
|
165
|
+
transcriptPath,
|
|
166
|
+
sessionId
|
|
167
|
+
});
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Finds Claude Code executable in common locations
|
|
173
|
+
*/
|
|
174
|
+
findClaudeExecutable() {
|
|
175
|
+
const possiblePaths = [
|
|
176
|
+
'/opt/homebrew/bin/claude',
|
|
177
|
+
'/usr/local/bin/claude',
|
|
178
|
+
process.env.CLAUDE_CODE_PATH,
|
|
179
|
+
].filter(Boolean);
|
|
180
|
+
for (const path of possiblePaths) {
|
|
181
|
+
if (path && fs.existsSync(path)) {
|
|
182
|
+
return path;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Finds MCP configuration file
|
|
189
|
+
*/
|
|
190
|
+
findMCPConfig() {
|
|
191
|
+
const possibleConfigs = [
|
|
192
|
+
join(process.cwd(), '.mcp.json'),
|
|
193
|
+
join(os.homedir(), '.claude.json'),
|
|
194
|
+
join(os.homedir(), '.claude', '.mcp.json'),
|
|
195
|
+
];
|
|
196
|
+
const mcpConfigPath = possibleConfigs.find(fs.existsSync);
|
|
197
|
+
return mcpConfigPath || join(os.homedir(), '.claude.json');
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Processes Claude response to extract summaries
|
|
201
|
+
*/
|
|
202
|
+
async processClaudeResponse(response) {
|
|
203
|
+
const summaries = [];
|
|
204
|
+
let fullContent = '';
|
|
205
|
+
if (response && typeof response === 'object' && Symbol.asyncIterator in response) {
|
|
206
|
+
// Handle streaming response
|
|
207
|
+
for await (const message of response) {
|
|
208
|
+
if (message?.content)
|
|
209
|
+
fullContent += message.content;
|
|
210
|
+
if (message?.text)
|
|
211
|
+
fullContent += message.text;
|
|
212
|
+
if (message?.data)
|
|
213
|
+
fullContent += message.data;
|
|
214
|
+
if (message?.message?.content) {
|
|
215
|
+
if (Array.isArray(message.message.content)) {
|
|
216
|
+
message.message.content.forEach((item) => {
|
|
217
|
+
if (item.type === 'text' && item.text) {
|
|
218
|
+
fullContent += item.text;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (message?.type === 'result' && message?.result) {
|
|
224
|
+
fullContent = message.result;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (typeof response === 'string') {
|
|
229
|
+
fullContent = response;
|
|
230
|
+
}
|
|
231
|
+
else if (Array.isArray(response)) {
|
|
232
|
+
response.forEach((item) => {
|
|
233
|
+
if (item?.session_id && item?.summary && item?.nodes && item?.keywords) {
|
|
234
|
+
summaries.push(item);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else if (typeof response === 'object' && response !== null) {
|
|
239
|
+
const obj = response;
|
|
240
|
+
if (obj.session_id && obj.summary && obj.nodes && obj.keywords) {
|
|
241
|
+
summaries.push(obj);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Parse JSONL content if we have it
|
|
245
|
+
if (fullContent && summaries.length === 0) {
|
|
246
|
+
const lines = fullContent.split('\n');
|
|
247
|
+
lines.forEach((line) => {
|
|
248
|
+
const trimmed = line.trim();
|
|
249
|
+
if (!trimmed)
|
|
250
|
+
return;
|
|
251
|
+
try {
|
|
252
|
+
const parsed = JSON.parse(trimmed);
|
|
253
|
+
if (parsed.session_id && parsed.summary && parsed.nodes && parsed.keywords) {
|
|
254
|
+
summaries.push(parsed);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
if (this.debugMode) {
|
|
259
|
+
log.debug(`Failed to parse JSONL line: ${e.message}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return summaries;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Formats conversation messages for analysis prompt
|
|
268
|
+
*/
|
|
269
|
+
formatConversationForPrompt(messages) {
|
|
270
|
+
const jsonlLines = [];
|
|
271
|
+
messages.forEach((m, index) => {
|
|
272
|
+
let role = 'unknown';
|
|
273
|
+
const messageType = m.type;
|
|
274
|
+
if (m.type === 'assistant') {
|
|
275
|
+
role = 'assistant';
|
|
276
|
+
}
|
|
277
|
+
else if (m.type === 'user') {
|
|
278
|
+
role = 'user';
|
|
279
|
+
}
|
|
280
|
+
else if (m.type === 'result') {
|
|
281
|
+
role = 'system';
|
|
282
|
+
}
|
|
283
|
+
else if (m.type === 'system') {
|
|
284
|
+
role = 'system';
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
role = m.message?.role || m.role || 'unknown';
|
|
288
|
+
}
|
|
289
|
+
const content = this.extractContent(m);
|
|
290
|
+
if (!content || content.trim() === '') {
|
|
291
|
+
if (this.debugMode) {
|
|
292
|
+
log.debug(`Skipping message ${index + 1}: empty content`);
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const timestamp = this.normalizeTimestamp(m);
|
|
297
|
+
const messageObj = {
|
|
298
|
+
type: messageType,
|
|
299
|
+
role: role,
|
|
300
|
+
content: content,
|
|
301
|
+
};
|
|
302
|
+
if (m.uuid)
|
|
303
|
+
messageObj.uuid = m.uuid;
|
|
304
|
+
if (m.session_id)
|
|
305
|
+
messageObj.session_id = m.session_id;
|
|
306
|
+
if (m.parent_tool_use_id)
|
|
307
|
+
messageObj.parent_tool_use_id = m.parent_tool_use_id;
|
|
308
|
+
if (timestamp)
|
|
309
|
+
messageObj.ts = timestamp;
|
|
310
|
+
try {
|
|
311
|
+
jsonlLines.push(JSON.stringify(messageObj));
|
|
312
|
+
}
|
|
313
|
+
catch (e) {
|
|
314
|
+
const safeObj = {
|
|
315
|
+
type: messageType,
|
|
316
|
+
role: role,
|
|
317
|
+
content: String(content).replace(/[\u0000-\u001F\u007F-\u009F]/g, ''),
|
|
318
|
+
error: 'content_sanitized',
|
|
319
|
+
};
|
|
320
|
+
if (m.uuid)
|
|
321
|
+
safeObj.uuid = m.uuid;
|
|
322
|
+
if (m.session_id)
|
|
323
|
+
safeObj.session_id = m.session_id;
|
|
324
|
+
if (timestamp)
|
|
325
|
+
safeObj.ts = timestamp;
|
|
326
|
+
jsonlLines.push(JSON.stringify(safeObj));
|
|
327
|
+
if (this.debugMode) {
|
|
328
|
+
log.debug(`Message ${index + 1} sanitized due to JSON error: ${e.message}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
if (this.debugMode) {
|
|
333
|
+
log.debug(`Field filtering complete: ${jsonlLines.length} messages processed`);
|
|
334
|
+
}
|
|
335
|
+
return `\`\`\`\n${jsonlLines.join('\n')}\n\`\`\`\n\n* All dates/times are in UTC`;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Extracts content from message object
|
|
339
|
+
*/
|
|
340
|
+
extractContent(m) {
|
|
341
|
+
if (m.type === 'assistant' || m.type === 'user') {
|
|
342
|
+
const messageContent = m.message?.content;
|
|
343
|
+
if (messageContent) {
|
|
344
|
+
if (Array.isArray(messageContent)) {
|
|
345
|
+
return messageContent
|
|
346
|
+
.map((item) => {
|
|
347
|
+
if (typeof item === 'string')
|
|
348
|
+
return item;
|
|
349
|
+
if (item.text)
|
|
350
|
+
return item.text;
|
|
351
|
+
if (item.content)
|
|
352
|
+
return item.content;
|
|
353
|
+
return '';
|
|
354
|
+
})
|
|
355
|
+
.filter(Boolean)
|
|
356
|
+
.join(' ');
|
|
357
|
+
}
|
|
358
|
+
return String(messageContent).trim();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else if (m.type === 'result') {
|
|
362
|
+
if (m.subtype === 'success' && m.result) {
|
|
363
|
+
return `[Result: ${m.result}]`;
|
|
364
|
+
}
|
|
365
|
+
else if (m.subtype === 'error_max_turns') {
|
|
366
|
+
return '[Error: Maximum turns reached]';
|
|
367
|
+
}
|
|
368
|
+
else if (m.subtype === 'error_during_execution') {
|
|
369
|
+
return '[Error during execution]';
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else if (m.type === 'system' && m.subtype === 'init') {
|
|
373
|
+
return `[System initialized: ${m.model}, tools: ${m.tools?.length || 0}, MCP servers: ${m.mcp_servers?.length || 0}]`;
|
|
374
|
+
}
|
|
375
|
+
let content = m.message?.content || m.content || '';
|
|
376
|
+
if (Array.isArray(content)) {
|
|
377
|
+
content = content
|
|
378
|
+
.map((item) => {
|
|
379
|
+
if (typeof item === 'string')
|
|
380
|
+
return item;
|
|
381
|
+
if (typeof item === 'object' && item !== null) {
|
|
382
|
+
const obj = item;
|
|
383
|
+
if (obj.text)
|
|
384
|
+
return obj.text;
|
|
385
|
+
if (obj.content)
|
|
386
|
+
return obj.content;
|
|
387
|
+
}
|
|
388
|
+
return '';
|
|
389
|
+
})
|
|
390
|
+
.filter(Boolean)
|
|
391
|
+
.join(' ');
|
|
392
|
+
}
|
|
393
|
+
if (m.toolUseResult) {
|
|
394
|
+
const toolSummary = this.summarizeToolResult(m.toolUseResult, String(content));
|
|
395
|
+
if (toolSummary) {
|
|
396
|
+
content = content ? `${content}\n\n${toolSummary}` : toolSummary;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return String(content).trim();
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Summarizes tool use results
|
|
403
|
+
*/
|
|
404
|
+
summarizeToolResult(toolResult, existingContent) {
|
|
405
|
+
const summaryParts = [];
|
|
406
|
+
if (toolResult.stdout) {
|
|
407
|
+
const stdout = String(toolResult.stdout);
|
|
408
|
+
if (stdout.length > 200) {
|
|
409
|
+
const lineCount = stdout.split('\n').length;
|
|
410
|
+
const charCount = stdout.length;
|
|
411
|
+
const lines = stdout.split('\n');
|
|
412
|
+
const preview = lines.slice(0, 3).join('\n');
|
|
413
|
+
const suffix = lines.length > 6 ? `\n...\n${lines.slice(-2).join('\n')}` : '';
|
|
414
|
+
summaryParts.push(`Result: ${preview}${suffix} (${lineCount} lines, ${charCount} chars)`);
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
summaryParts.push(`Result: ${stdout}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (toolResult.stderr && toolResult.stderr.trim()) {
|
|
421
|
+
summaryParts.push(`Error: ${toolResult.stderr.substring(0, 150)}${toolResult.stderr.length > 150 ? '...' : ''}`);
|
|
422
|
+
}
|
|
423
|
+
if (toolResult.interrupted) {
|
|
424
|
+
summaryParts.push('(interrupted)');
|
|
425
|
+
}
|
|
426
|
+
if (toolResult.isImage) {
|
|
427
|
+
summaryParts.push('(image output)');
|
|
428
|
+
}
|
|
429
|
+
return summaryParts.join('\n');
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Normalizes timestamp formats
|
|
433
|
+
*/
|
|
434
|
+
normalizeTimestamp(m) {
|
|
435
|
+
const ts = m.timestamp || m.message?.timestamp || m.created_at || m.message?.created_at;
|
|
436
|
+
if (!ts)
|
|
437
|
+
return '';
|
|
438
|
+
try {
|
|
439
|
+
const date = new Date(ts);
|
|
440
|
+
if (isNaN(date.getTime()))
|
|
441
|
+
return '';
|
|
442
|
+
return date.toISOString().slice(0, 19).replace('T', ' ');
|
|
443
|
+
}
|
|
444
|
+
catch (e) {
|
|
445
|
+
if (this.debugMode) {
|
|
446
|
+
log.debug(`Invalid timestamp format: ${ts}`);
|
|
447
|
+
}
|
|
448
|
+
return '';
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Creates an archive file of the original transcript
|
|
453
|
+
*/
|
|
454
|
+
createArchive(transcriptPath, projectName, sessionId, content) {
|
|
455
|
+
const projectArchiveDir = this.paths.getProjectArchiveDir(projectName);
|
|
456
|
+
PathResolver.ensureDirectory(projectArchiveDir);
|
|
457
|
+
const archivePath = join(projectArchiveDir, `${sessionId}.jsonl.archive`);
|
|
458
|
+
fs.writeFileSync(archivePath, content);
|
|
459
|
+
if (this.debugMode) {
|
|
460
|
+
log.debug(`📦 Created archive: ${archivePath}`);
|
|
461
|
+
}
|
|
462
|
+
return archivePath;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Appends compression summaries to the index file
|
|
466
|
+
*/
|
|
467
|
+
appendToIndex(summaries, projectName, sessionId, messages, archivePath) {
|
|
468
|
+
const indexPath = this.paths.getIndexPath();
|
|
469
|
+
const indexDir = path.dirname(indexPath);
|
|
470
|
+
PathResolver.ensureDirectory(indexDir);
|
|
471
|
+
const messageCount = messages.length;
|
|
472
|
+
const indexEntries = summaries.map((entry) => ({
|
|
473
|
+
...entry,
|
|
474
|
+
session_id: sessionId,
|
|
475
|
+
project: projectName,
|
|
476
|
+
message_count: entry.message_count || messageCount,
|
|
477
|
+
archive_path: archivePath,
|
|
478
|
+
}));
|
|
479
|
+
const indexContent = indexEntries.map((entry) => JSON.stringify(entry)).join('\n') + '\n';
|
|
480
|
+
fs.appendFileSync(indexPath, indexContent);
|
|
481
|
+
if (this.debugMode) {
|
|
482
|
+
log.debug(`📝 Appended ${indexEntries.length} entries to index`);
|
|
483
|
+
log.debug(`Index path: ${indexPath}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common utilities for the memory compression system - CONSOLIDATED
|
|
3
|
+
*
|
|
4
|
+
* This module now re-exports the main utility classes to maintain backward compatibility.
|
|
5
|
+
* The duplicate implementations have been consolidated into the main utility files:
|
|
6
|
+
*
|
|
7
|
+
* - PathResolver -> src/utils/PathResolver.ts
|
|
8
|
+
* - SettingsManager -> src/utils/SettingsManager.ts
|
|
9
|
+
* - HookDetector -> src/utils/HookDetector.ts
|
|
10
|
+
*/
|
|
11
|
+
export { PathResolver } from './PathResolver.js';
|
|
12
|
+
export { SettingsManager } from './SettingsManager.js';
|
|
13
|
+
export { HookDetector } from './HookDetector.js';
|
|
14
|
+
export type { SettingsLocation } from './PathResolver.js';
|
|
15
|
+
export type { ClaudeSettings, SettingsResult } from './SettingsManager.js';
|
|
16
|
+
export type { HookDetectionResult, HookInfo } from './HookDetector.js';
|
|
17
|
+
export interface InstallOptions {
|
|
18
|
+
local?: boolean;
|
|
19
|
+
project?: boolean;
|
|
20
|
+
force?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface ClaudeMemPaths {
|
|
23
|
+
baseDir: string;
|
|
24
|
+
hooksDir: string;
|
|
25
|
+
indexDir: string;
|
|
26
|
+
archivesDir: string;
|
|
27
|
+
logsDir: string;
|
|
28
|
+
settingsFile: string;
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common utilities for the memory compression system - CONSOLIDATED
|
|
3
|
+
*
|
|
4
|
+
* This module now re-exports the main utility classes to maintain backward compatibility.
|
|
5
|
+
* The duplicate implementations have been consolidated into the main utility files:
|
|
6
|
+
*
|
|
7
|
+
* - PathResolver -> src/utils/PathResolver.ts
|
|
8
|
+
* - SettingsManager -> src/utils/SettingsManager.ts
|
|
9
|
+
* - HookDetector -> src/utils/HookDetector.ts
|
|
10
|
+
*/
|
|
11
|
+
// Re-export main utility classes for backward compatibility
|
|
12
|
+
export { PathResolver } from './PathResolver.js';
|
|
13
|
+
export { SettingsManager } from './SettingsManager.js';
|
|
14
|
+
export { HookDetector } from './HookDetector.js';
|