psychmem 1.0.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 (93) hide show
  1. package/README.md +632 -0
  2. package/dist/adapters/claude-code/index.d.ts +125 -0
  3. package/dist/adapters/claude-code/index.d.ts.map +1 -0
  4. package/dist/adapters/claude-code/index.js +398 -0
  5. package/dist/adapters/claude-code/index.js.map +1 -0
  6. package/dist/adapters/opencode/index.d.ts +50 -0
  7. package/dist/adapters/opencode/index.d.ts.map +1 -0
  8. package/dist/adapters/opencode/index.js +793 -0
  9. package/dist/adapters/opencode/index.js.map +1 -0
  10. package/dist/adapters/types.d.ts +226 -0
  11. package/dist/adapters/types.d.ts.map +1 -0
  12. package/dist/adapters/types.js +6 -0
  13. package/dist/adapters/types.js.map +1 -0
  14. package/dist/cli.d.ts +19 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +461 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/hooks/index.d.ts +92 -0
  19. package/dist/hooks/index.d.ts.map +1 -0
  20. package/dist/hooks/index.js +304 -0
  21. package/dist/hooks/index.js.map +1 -0
  22. package/dist/hooks/post-tool-use.d.ts +26 -0
  23. package/dist/hooks/post-tool-use.d.ts.map +1 -0
  24. package/dist/hooks/post-tool-use.js +69 -0
  25. package/dist/hooks/post-tool-use.js.map +1 -0
  26. package/dist/hooks/session-end.d.ts +32 -0
  27. package/dist/hooks/session-end.d.ts.map +1 -0
  28. package/dist/hooks/session-end.js +66 -0
  29. package/dist/hooks/session-end.js.map +1 -0
  30. package/dist/hooks/session-start.d.ts +55 -0
  31. package/dist/hooks/session-start.d.ts.map +1 -0
  32. package/dist/hooks/session-start.js +173 -0
  33. package/dist/hooks/session-start.js.map +1 -0
  34. package/dist/hooks/stop.d.ts +72 -0
  35. package/dist/hooks/stop.d.ts.map +1 -0
  36. package/dist/hooks/stop.js +273 -0
  37. package/dist/hooks/stop.js.map +1 -0
  38. package/dist/index.d.ts +114 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +191 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/memory/context-sweep.d.ts +107 -0
  43. package/dist/memory/context-sweep.d.ts.map +1 -0
  44. package/dist/memory/context-sweep.js +557 -0
  45. package/dist/memory/context-sweep.js.map +1 -0
  46. package/dist/memory/patterns.d.ts +106 -0
  47. package/dist/memory/patterns.d.ts.map +1 -0
  48. package/dist/memory/patterns.js +613 -0
  49. package/dist/memory/patterns.js.map +1 -0
  50. package/dist/memory/selective-memory.d.ts +78 -0
  51. package/dist/memory/selective-memory.d.ts.map +1 -0
  52. package/dist/memory/selective-memory.js +227 -0
  53. package/dist/memory/selective-memory.js.map +1 -0
  54. package/dist/memory/structural-analyzer.d.ts +75 -0
  55. package/dist/memory/structural-analyzer.d.ts.map +1 -0
  56. package/dist/memory/structural-analyzer.js +359 -0
  57. package/dist/memory/structural-analyzer.js.map +1 -0
  58. package/dist/retrieval/index.d.ts +106 -0
  59. package/dist/retrieval/index.d.ts.map +1 -0
  60. package/dist/retrieval/index.js +291 -0
  61. package/dist/retrieval/index.js.map +1 -0
  62. package/dist/storage/database.d.ts +138 -0
  63. package/dist/storage/database.d.ts.map +1 -0
  64. package/dist/storage/database.js +748 -0
  65. package/dist/storage/database.js.map +1 -0
  66. package/dist/storage/sqlite-adapter.d.ts +35 -0
  67. package/dist/storage/sqlite-adapter.d.ts.map +1 -0
  68. package/dist/storage/sqlite-adapter.js +103 -0
  69. package/dist/storage/sqlite-adapter.js.map +1 -0
  70. package/dist/transcript/index.d.ts +8 -0
  71. package/dist/transcript/index.d.ts.map +1 -0
  72. package/dist/transcript/index.js +6 -0
  73. package/dist/transcript/index.js.map +1 -0
  74. package/dist/transcript/parser.d.ts +93 -0
  75. package/dist/transcript/parser.d.ts.map +1 -0
  76. package/dist/transcript/parser.js +373 -0
  77. package/dist/transcript/parser.js.map +1 -0
  78. package/dist/transcript/sweep.d.ts +75 -0
  79. package/dist/transcript/sweep.d.ts.map +1 -0
  80. package/dist/transcript/sweep.js +202 -0
  81. package/dist/transcript/sweep.js.map +1 -0
  82. package/dist/types/index.d.ts +328 -0
  83. package/dist/types/index.d.ts.map +1 -0
  84. package/dist/types/index.js +80 -0
  85. package/dist/types/index.js.map +1 -0
  86. package/dist/utils/paths.d.ts +19 -0
  87. package/dist/utils/paths.d.ts.map +1 -0
  88. package/dist/utils/paths.js +43 -0
  89. package/dist/utils/paths.js.map +1 -0
  90. package/hooks/hooks.json +54 -0
  91. package/package.json +83 -0
  92. package/plugin.js +45 -0
  93. package/plugin.json +19 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Transcript Parser for Claude Code
3
+ *
4
+ * Parses Claude Code's JSONL transcript files incrementally using watermark tracking.
5
+ * The watermark is a byte offset to avoid re-processing already-seen content.
6
+ *
7
+ * Claude Code transcript format (JSONL):
8
+ * Each line is a JSON object with various message types. Schema is not fully documented,
9
+ * so we use flexible parsing with fallback handling.
10
+ */
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import * as readline from 'readline';
14
+ // =============================================================================
15
+ // Parser Implementation
16
+ // =============================================================================
17
+ export class TranscriptParser {
18
+ /**
19
+ * Parse transcript file from a given byte offset
20
+ * Returns entries from that point forward and the new watermark
21
+ */
22
+ async parseFromWatermark(transcriptPath, watermark = 0) {
23
+ // Resolve path if needed
24
+ const resolvedPath = this.resolvePath(transcriptPath);
25
+ // Check if file exists
26
+ if (!fs.existsSync(resolvedPath)) {
27
+ return {
28
+ entries: [],
29
+ newWatermark: watermark,
30
+ linesProcessed: 0,
31
+ };
32
+ }
33
+ const entries = [];
34
+ let newWatermark = watermark;
35
+ let linesProcessed = 0;
36
+ // Open file and seek to watermark position
37
+ const fileHandle = await fs.promises.open(resolvedPath, 'r');
38
+ try {
39
+ const stats = await fileHandle.stat();
40
+ // If watermark is beyond file size, no new content
41
+ if (watermark >= stats.size) {
42
+ return {
43
+ entries: [],
44
+ newWatermark: watermark,
45
+ linesProcessed: 0,
46
+ };
47
+ }
48
+ // Create read stream from watermark position
49
+ const stream = fs.createReadStream(resolvedPath, {
50
+ start: watermark,
51
+ encoding: 'utf-8',
52
+ });
53
+ // Read raw bytes separately to detect CRLF vs LF line endings
54
+ const rawStream = fs.createReadStream(resolvedPath, {
55
+ start: watermark,
56
+ });
57
+ // Detect line endings by scanning a small buffer
58
+ let crlfDetected = false;
59
+ let crlfSampleDone = false;
60
+ const sampleChunks = [];
61
+ rawStream.on('data', (chunk) => {
62
+ if (!crlfSampleDone) {
63
+ sampleChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
64
+ const combined = Buffer.concat(sampleChunks);
65
+ if (combined.includes(0x0d /* \r */)) {
66
+ crlfDetected = true;
67
+ crlfSampleDone = true;
68
+ }
69
+ else if (combined.length >= 4096) {
70
+ crlfSampleDone = true;
71
+ }
72
+ }
73
+ });
74
+ rawStream.on('error', () => { crlfSampleDone = true; });
75
+ await new Promise((resolve) => rawStream.on('close', resolve));
76
+ const rl = readline.createInterface({
77
+ input: stream,
78
+ crlfDelay: Infinity,
79
+ });
80
+ let bytesRead = watermark;
81
+ // readline with crlfDelay:Infinity strips \r, so each line's byte length
82
+ // needs +1 for \n (LF files) or +2 for \r\n (CRLF files)
83
+ const newlineBytes = crlfDetected ? 2 : 1;
84
+ for await (const line of rl) {
85
+ // Track byte position (line content + newline byte(s))
86
+ bytesRead += Buffer.byteLength(line, 'utf-8') + newlineBytes;
87
+ if (line.trim().length === 0)
88
+ continue;
89
+ try {
90
+ const entry = this.parseLine(line);
91
+ if (entry) {
92
+ entries.push(entry);
93
+ }
94
+ linesProcessed++;
95
+ }
96
+ catch (e) {
97
+ // Skip malformed lines but continue processing
98
+ console.error(`[TranscriptParser] Skipping malformed line: ${e}`);
99
+ }
100
+ }
101
+ newWatermark = bytesRead;
102
+ }
103
+ finally {
104
+ await fileHandle.close();
105
+ }
106
+ return {
107
+ entries,
108
+ newWatermark,
109
+ linesProcessed,
110
+ };
111
+ }
112
+ /**
113
+ * Parse a single JSONL line into a transcript entry
114
+ * Handles various Claude Code message formats flexibly
115
+ */
116
+ parseLine(line) {
117
+ const json = JSON.parse(line);
118
+ // Detect message type and extract content
119
+ const type = this.detectEntryType(json);
120
+ const content = this.extractContent(json);
121
+ if (!content || content.trim().length === 0) {
122
+ return null;
123
+ }
124
+ // Build entry with only defined optional properties
125
+ const timestamp = this.extractTimestamp(json);
126
+ const role = this.extractRole(json);
127
+ const toolName = this.extractToolName(json);
128
+ const toolInput = this.extractToolInput(json);
129
+ const toolOutput = this.extractToolOutput(json);
130
+ const entry = {
131
+ type,
132
+ content,
133
+ rawJson: json,
134
+ };
135
+ // Only add optional properties if defined
136
+ if (timestamp !== undefined)
137
+ entry.timestamp = timestamp;
138
+ if (role !== undefined)
139
+ entry.role = role;
140
+ if (toolName !== undefined)
141
+ entry.toolName = toolName;
142
+ if (toolInput !== undefined)
143
+ entry.toolInput = toolInput;
144
+ if (toolOutput !== undefined)
145
+ entry.toolOutput = toolOutput;
146
+ return entry;
147
+ }
148
+ /**
149
+ * Detect the type of transcript entry
150
+ */
151
+ detectEntryType(json) {
152
+ // Check for explicit type field
153
+ const type = json.type;
154
+ if (type === 'user' || json.role === 'user') {
155
+ return 'user_message';
156
+ }
157
+ if (type === 'assistant' || json.role === 'assistant') {
158
+ // Check if it's a tool use
159
+ if (json.tool_use || json.toolName || this.hasToolUseContent(json)) {
160
+ return 'tool_use';
161
+ }
162
+ return 'assistant_message';
163
+ }
164
+ if (type === 'tool_result' || json.tool_result || json.toolOutput !== undefined) {
165
+ return 'tool_result';
166
+ }
167
+ if (type === 'system' || json.role === 'system') {
168
+ return 'system';
169
+ }
170
+ // Check content structure for tool patterns
171
+ if (json.tool_use || json.name || json.input) {
172
+ return 'tool_use';
173
+ }
174
+ // Default based on role
175
+ const role = json.role;
176
+ if (role === 'user')
177
+ return 'user_message';
178
+ if (role === 'assistant')
179
+ return 'assistant_message';
180
+ return 'unknown';
181
+ }
182
+ /**
183
+ * Check if json contains tool use content
184
+ */
185
+ hasToolUseContent(json) {
186
+ // Check various possible locations for tool use
187
+ if (json.content && Array.isArray(json.content)) {
188
+ return json.content.some((c) => typeof c === 'object' && c !== null && 'type' in c && c.type === 'tool_use');
189
+ }
190
+ return false;
191
+ }
192
+ /**
193
+ * Extract text content from various formats
194
+ */
195
+ extractContent(json) {
196
+ // Direct content string
197
+ if (typeof json.content === 'string') {
198
+ return json.content;
199
+ }
200
+ // Content array (Claude API format)
201
+ if (Array.isArray(json.content)) {
202
+ const textParts = [];
203
+ for (const part of json.content) {
204
+ if (typeof part === 'string') {
205
+ textParts.push(part);
206
+ }
207
+ else if (typeof part === 'object' && part !== null) {
208
+ const obj = part;
209
+ // Text block
210
+ if (obj.type === 'text' && typeof obj.text === 'string') {
211
+ textParts.push(obj.text);
212
+ }
213
+ // Tool use block
214
+ if (obj.type === 'tool_use') {
215
+ const toolName = obj.name || 'unknown';
216
+ const input = obj.input ? JSON.stringify(obj.input) : '';
217
+ textParts.push(`[Tool: ${toolName}] ${input}`);
218
+ }
219
+ // Tool result block
220
+ if (obj.type === 'tool_result') {
221
+ const content = typeof obj.content === 'string'
222
+ ? obj.content
223
+ : JSON.stringify(obj.content);
224
+ textParts.push(`[Tool Result] ${content}`);
225
+ }
226
+ }
227
+ }
228
+ return textParts.join('\n');
229
+ }
230
+ // Message field
231
+ if (typeof json.message === 'string') {
232
+ return json.message;
233
+ }
234
+ // Text field
235
+ if (typeof json.text === 'string') {
236
+ return json.text;
237
+ }
238
+ // Tool output
239
+ if (json.toolOutput !== undefined) {
240
+ return typeof json.toolOutput === 'string'
241
+ ? json.toolOutput
242
+ : JSON.stringify(json.toolOutput);
243
+ }
244
+ // Fallback: stringify the whole object
245
+ return '';
246
+ }
247
+ /**
248
+ * Extract timestamp from entry
249
+ */
250
+ extractTimestamp(json) {
251
+ if (typeof json.timestamp === 'string')
252
+ return json.timestamp;
253
+ if (typeof json.created_at === 'string')
254
+ return json.created_at;
255
+ if (typeof json.time === 'string')
256
+ return json.time;
257
+ return undefined;
258
+ }
259
+ /**
260
+ * Extract role from entry
261
+ */
262
+ extractRole(json) {
263
+ const role = json.role;
264
+ if (role === 'user' || role === 'assistant' || role === 'system' || role === 'tool') {
265
+ return role;
266
+ }
267
+ return undefined;
268
+ }
269
+ /**
270
+ * Extract tool name
271
+ */
272
+ extractToolName(json) {
273
+ if (typeof json.name === 'string')
274
+ return json.name;
275
+ if (typeof json.toolName === 'string')
276
+ return json.toolName;
277
+ if (typeof json.tool_name === 'string')
278
+ return json.tool_name;
279
+ // Check in content array
280
+ if (Array.isArray(json.content)) {
281
+ for (const part of json.content) {
282
+ if (typeof part === 'object' && part !== null) {
283
+ const obj = part;
284
+ if (obj.type === 'tool_use' && typeof obj.name === 'string') {
285
+ return obj.name;
286
+ }
287
+ }
288
+ }
289
+ }
290
+ return undefined;
291
+ }
292
+ /**
293
+ * Extract tool input
294
+ */
295
+ extractToolInput(json) {
296
+ if (json.input !== undefined)
297
+ return json.input;
298
+ if (json.toolInput !== undefined)
299
+ return json.toolInput;
300
+ if (json.tool_input !== undefined)
301
+ return json.tool_input;
302
+ // Check in content array
303
+ if (Array.isArray(json.content)) {
304
+ for (const part of json.content) {
305
+ if (typeof part === 'object' && part !== null) {
306
+ const obj = part;
307
+ if (obj.type === 'tool_use' && obj.input !== undefined) {
308
+ return obj.input;
309
+ }
310
+ }
311
+ }
312
+ }
313
+ return undefined;
314
+ }
315
+ /**
316
+ * Extract tool output
317
+ */
318
+ extractToolOutput(json) {
319
+ if (json.output !== undefined)
320
+ return json.output;
321
+ if (json.toolOutput !== undefined)
322
+ return json.toolOutput;
323
+ if (json.tool_output !== undefined)
324
+ return json.tool_output;
325
+ // Check in content array for tool_result
326
+ if (Array.isArray(json.content)) {
327
+ for (const part of json.content) {
328
+ if (typeof part === 'object' && part !== null) {
329
+ const obj = part;
330
+ if (obj.type === 'tool_result') {
331
+ return obj.content;
332
+ }
333
+ }
334
+ }
335
+ }
336
+ return undefined;
337
+ }
338
+ /**
339
+ * Resolve path, expanding ~ to home directory
340
+ */
341
+ resolvePath(filePath) {
342
+ if (filePath.startsWith('~')) {
343
+ const home = process.env.HOME || process.env.USERPROFILE || '';
344
+ return path.join(home, filePath.slice(1));
345
+ }
346
+ return path.resolve(filePath);
347
+ }
348
+ /**
349
+ * Get conversation text from entries (for context sweep)
350
+ */
351
+ static entriesToConversationText(entries) {
352
+ return entries
353
+ .map(entry => {
354
+ const rolePrefix = entry.role ? `[${entry.role}] ` : '';
355
+ return `${rolePrefix}${entry.content}`;
356
+ })
357
+ .join('\n\n');
358
+ }
359
+ /**
360
+ * Filter entries to only user and assistant messages (no tool details)
361
+ */
362
+ static filterConversation(entries) {
363
+ return entries.filter(e => e.type === 'user_message' || e.type === 'assistant_message');
364
+ }
365
+ /**
366
+ * Get only new entries since last check (stateless helper)
367
+ */
368
+ static async getNewEntries(transcriptPath, lastWatermark) {
369
+ const parser = new TranscriptParser();
370
+ return parser.parseFromWatermark(transcriptPath, lastWatermark);
371
+ }
372
+ }
373
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/transcript/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Transcript Sweep - Memory extraction from transcript with deduplication
3
+ *
4
+ * This module coordinates:
5
+ * 1. Parsing new transcript entries (using watermark)
6
+ * 2. Running context sweep on new content
7
+ * 3. Deduplicating against existing session memories (70% keyword overlap)
8
+ * 4. Limiting to maxMemoriesPerStop (based on Cowan's working memory: 4±1)
9
+ */
10
+ import type { MemoryCandidate, MemoryUnit, PsychMemConfig } from '../types/index.js';
11
+ export interface TranscriptSweepResult {
12
+ /** Memory candidates after deduplication */
13
+ candidates: MemoryCandidate[];
14
+ /** New watermark to save */
15
+ newWatermark: number;
16
+ /** Number of candidates before deduplication */
17
+ rawCandidateCount: number;
18
+ /** Number filtered by deduplication */
19
+ deduplicatedCount: number;
20
+ /** Number filtered by limit */
21
+ limitedCount: number;
22
+ }
23
+ export interface TranscriptSweepOptions {
24
+ /** Session ID for deduplication lookup */
25
+ sessionId: string;
26
+ /** Existing memories for this session */
27
+ existingMemories: MemoryUnit[];
28
+ /** Configuration */
29
+ config?: Partial<PsychMemConfig>;
30
+ }
31
+ export declare class TranscriptSweep {
32
+ private parser;
33
+ private contextSweep;
34
+ private config;
35
+ constructor(config?: Partial<PsychMemConfig>);
36
+ /**
37
+ * Main entry point: extract memories from transcript since last watermark
38
+ * with deduplication and limits
39
+ */
40
+ sweepTranscript(transcriptPath: string, watermark: number, options: TranscriptSweepOptions): Promise<TranscriptSweepResult>;
41
+ /**
42
+ * Extract memory candidates from conversation text
43
+ * Creates synthetic events for context sweep compatibility
44
+ */
45
+ private extractFromText;
46
+ /**
47
+ * Map transcript entry type to hook type for context sweep
48
+ */
49
+ private entryTypeToHookType;
50
+ /**
51
+ * Deduplicate candidates against existing memories using keyword overlap
52
+ */
53
+ private deduplicateCandidates;
54
+ /**
55
+ * Apply the max memories per stop limit
56
+ * Sort by importance and take top N
57
+ */
58
+ private applyLimit;
59
+ /**
60
+ * Extract keywords from text for overlap calculation
61
+ * Returns a Set of normalized keywords
62
+ */
63
+ private extractKeywords;
64
+ /**
65
+ * Calculate Jaccard similarity (overlap) between two keyword sets
66
+ * Returns a value between 0 and 1
67
+ */
68
+ private calculateKeywordOverlap;
69
+ /**
70
+ * Static helper: quick check if transcript has new content
71
+ */
72
+ static hasNewContent(transcriptPath: string, watermark: number): Promise<boolean>;
73
+ }
74
+ export { TranscriptParser } from './parser.js';
75
+ //# sourceMappingURL=sweep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sweep.d.ts","sourceRoot":"","sources":["../../src/transcript/sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACf,MAAM,mBAAmB,CAAC;AAS3B,MAAM,WAAW,qBAAqB;IACpC,4CAA4C;IAC5C,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,4BAA4B;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,gBAAgB,EAAE,UAAU,EAAE,CAAC;IAC/B,oBAAoB;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;CAClC;AAMD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,GAAE,OAAO,CAAC,cAAc,CAAM;IAMhD;;;OAGG;IACG,eAAe,CACnB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC;IAoCjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAgCvB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAY3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0C7B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAkBlB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAsBvB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAmB/B;;OAEG;WACU,aAAa,CACxB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;CAIpB;AAMD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Transcript Sweep - Memory extraction from transcript with deduplication
3
+ *
4
+ * This module coordinates:
5
+ * 1. Parsing new transcript entries (using watermark)
6
+ * 2. Running context sweep on new content
7
+ * 3. Deduplicating against existing session memories (70% keyword overlap)
8
+ * 4. Limiting to maxMemoriesPerStop (based on Cowan's working memory: 4±1)
9
+ */
10
+ import { TranscriptParser } from './parser.js';
11
+ import { ContextSweep } from '../memory/context-sweep.js';
12
+ import { DEFAULT_CONFIG } from '../types/index.js';
13
+ // =============================================================================
14
+ // Transcript Sweep Implementation
15
+ // =============================================================================
16
+ export class TranscriptSweep {
17
+ parser;
18
+ contextSweep;
19
+ config;
20
+ constructor(config = {}) {
21
+ this.config = { ...DEFAULT_CONFIG, ...config };
22
+ this.parser = new TranscriptParser();
23
+ this.contextSweep = new ContextSweep(this.config.sweep);
24
+ }
25
+ /**
26
+ * Main entry point: extract memories from transcript since last watermark
27
+ * with deduplication and limits
28
+ */
29
+ async sweepTranscript(transcriptPath, watermark, options) {
30
+ // 1. Parse new transcript entries
31
+ const parseResult = await this.parser.parseFromWatermark(transcriptPath, watermark);
32
+ if (parseResult.entries.length === 0) {
33
+ return {
34
+ candidates: [],
35
+ newWatermark: parseResult.newWatermark,
36
+ rawCandidateCount: 0,
37
+ deduplicatedCount: 0,
38
+ limitedCount: 0,
39
+ };
40
+ }
41
+ // 2. Convert entries to conversation text and run context sweep
42
+ const conversationText = TranscriptParser.entriesToConversationText(parseResult.entries);
43
+ const rawCandidates = this.extractFromText(conversationText, parseResult.entries);
44
+ // 3. Deduplicate against existing session memories
45
+ const dedupResult = this.deduplicateCandidates(rawCandidates, options.existingMemories);
46
+ // 4. Apply limit (maxMemoriesPerStop)
47
+ const limited = this.applyLimit(dedupResult.candidates);
48
+ return {
49
+ candidates: limited.candidates,
50
+ newWatermark: parseResult.newWatermark,
51
+ rawCandidateCount: rawCandidates.length,
52
+ deduplicatedCount: dedupResult.removedCount,
53
+ limitedCount: limited.removedCount,
54
+ };
55
+ }
56
+ /**
57
+ * Extract memory candidates from conversation text
58
+ * Creates synthetic events for context sweep compatibility
59
+ */
60
+ extractFromText(text, entries) {
61
+ // Create synthetic events for the context sweep
62
+ const syntheticEvents = entries.map((entry, index) => ({
63
+ id: `transcript-${index}`,
64
+ sessionId: 'transcript',
65
+ hookType: this.entryTypeToHookType(entry.type),
66
+ timestamp: entry.timestamp ? new Date(entry.timestamp) : new Date(),
67
+ content: entry.content,
68
+ toolName: entry.toolName,
69
+ toolInput: entry.toolInput ? JSON.stringify(entry.toolInput) : undefined,
70
+ toolOutput: entry.toolOutput ? JSON.stringify(entry.toolOutput) : undefined,
71
+ }));
72
+ // Also add a Stop event with full conversation for comprehensive analysis
73
+ syntheticEvents.push({
74
+ id: 'transcript-stop',
75
+ sessionId: 'transcript',
76
+ hookType: 'Stop',
77
+ timestamp: new Date(),
78
+ content: text,
79
+ toolName: undefined,
80
+ toolInput: undefined,
81
+ toolOutput: undefined,
82
+ });
83
+ // Run context sweep
84
+ return this.contextSweep.extractCandidates(syntheticEvents);
85
+ }
86
+ /**
87
+ * Map transcript entry type to hook type for context sweep
88
+ */
89
+ entryTypeToHookType(type) {
90
+ switch (type) {
91
+ case 'user_message':
92
+ return 'UserPromptSubmit';
93
+ case 'tool_use':
94
+ case 'tool_result':
95
+ return 'PostToolUse';
96
+ default:
97
+ return 'Stop';
98
+ }
99
+ }
100
+ /**
101
+ * Deduplicate candidates against existing memories using keyword overlap
102
+ */
103
+ deduplicateCandidates(candidates, existingMemories) {
104
+ if (existingMemories.length === 0) {
105
+ return { candidates, removedCount: 0 };
106
+ }
107
+ // Pre-compute keywords for existing memories
108
+ const existingKeywordSets = existingMemories.map(mem => ({
109
+ memory: mem,
110
+ keywords: this.extractKeywords(mem.summary),
111
+ }));
112
+ const filtered = [];
113
+ let removedCount = 0;
114
+ for (const candidate of candidates) {
115
+ const candidateKeywords = this.extractKeywords(candidate.summary);
116
+ // Check overlap with each existing memory
117
+ let isDuplicate = false;
118
+ for (const { keywords: existingKeywords } of existingKeywordSets) {
119
+ const overlap = this.calculateKeywordOverlap(candidateKeywords, existingKeywords);
120
+ if (overlap >= this.config.deduplicationThreshold) {
121
+ isDuplicate = true;
122
+ break;
123
+ }
124
+ }
125
+ if (isDuplicate) {
126
+ removedCount++;
127
+ }
128
+ else {
129
+ filtered.push(candidate);
130
+ }
131
+ }
132
+ return { candidates: filtered, removedCount };
133
+ }
134
+ /**
135
+ * Apply the max memories per stop limit
136
+ * Sort by importance and take top N
137
+ */
138
+ applyLimit(candidates) {
139
+ if (candidates.length <= this.config.maxMemoriesPerStop) {
140
+ return { candidates, removedCount: 0 };
141
+ }
142
+ // Sort by preliminary importance (descending)
143
+ const sorted = [...candidates].sort((a, b) => b.preliminaryImportance - a.preliminaryImportance);
144
+ const limited = sorted.slice(0, this.config.maxMemoriesPerStop);
145
+ const removedCount = candidates.length - limited.length;
146
+ return { candidates: limited, removedCount };
147
+ }
148
+ /**
149
+ * Extract keywords from text for overlap calculation
150
+ * Returns a Set of normalized keywords
151
+ */
152
+ extractKeywords(text) {
153
+ // Stopwords to filter out
154
+ const stopwords = new Set([
155
+ 'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
156
+ 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
157
+ 'should', 'may', 'might', 'can', 'to', 'of', 'in', 'for', 'on', 'with',
158
+ 'at', 'by', 'from', 'as', 'into', 'through', 'during', 'before', 'after',
159
+ 'this', 'that', 'these', 'those', 'it', 'its', 'they', 'them', 'their',
160
+ 'and', 'or', 'but', 'if', 'so', 'because', 'while', 'when', 'where',
161
+ 'what', 'which', 'who', 'how', 'why', 'all', 'each', 'some', 'any',
162
+ 'i', 'me', 'my', 'we', 'our', 'you', 'your', 'he', 'she', 'him', 'her',
163
+ ]);
164
+ const words = text
165
+ .toLowerCase()
166
+ .replace(/[^a-z0-9\s]/g, ' ')
167
+ .split(/\s+/)
168
+ .filter(word => word.length > 2 && !stopwords.has(word));
169
+ return new Set(words);
170
+ }
171
+ /**
172
+ * Calculate Jaccard similarity (overlap) between two keyword sets
173
+ * Returns a value between 0 and 1
174
+ */
175
+ calculateKeywordOverlap(setA, setB) {
176
+ if (setA.size === 0 || setB.size === 0) {
177
+ return 0;
178
+ }
179
+ let intersection = 0;
180
+ for (const word of setA) {
181
+ if (setB.has(word)) {
182
+ intersection++;
183
+ }
184
+ }
185
+ const union = setA.size + setB.size - intersection;
186
+ if (union === 0)
187
+ return 0;
188
+ return intersection / union;
189
+ }
190
+ /**
191
+ * Static helper: quick check if transcript has new content
192
+ */
193
+ static async hasNewContent(transcriptPath, watermark) {
194
+ const result = await TranscriptParser.getNewEntries(transcriptPath, watermark);
195
+ return result.entries.length > 0;
196
+ }
197
+ }
198
+ // =============================================================================
199
+ // Export index for module
200
+ // =============================================================================
201
+ export { TranscriptParser } from './parser.js';
202
+ //# sourceMappingURL=sweep.js.map