@rlabs-inc/memory 0.3.10 → 0.4.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.
- package/README.md +8 -0
- package/dist/index.js +32924 -11907
- package/dist/index.mjs +33020 -12003
- package/dist/server/index.js +22512 -1351
- package/dist/server/index.mjs +22719 -1558
- package/package.json +2 -1
- package/skills/memory-management.md +143 -154
- package/src/cli/commands/ingest.ts +227 -23
- package/src/cli/commands/migrate.ts +561 -80
- package/src/cli/index.ts +17 -4
- package/src/core/curator.ts +132 -22
- package/src/core/engine.test.ts +6 -14
- package/src/core/manager.ts +175 -1
- package/src/core/retrieval.ts +9 -22
- package/src/core/store.ts +24 -44
- package/src/migrations/v3-schema.ts +392 -0
- package/src/types/memory.ts +85 -123
- package/src/types/schema.ts +12 -11
- package/src/utils/logger.ts +10 -1
package/src/cli/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ ${c.bold('Commands:')}
|
|
|
23
23
|
${c.command('serve')} Start the memory server ${c.muted('(default)')}
|
|
24
24
|
${c.command('stats')} Show memory statistics
|
|
25
25
|
${c.command('install')} Set up hooks ${c.muted('(--claude or --gemini)')}
|
|
26
|
-
${c.command('ingest')} Ingest historical sessions into memory ${c.muted('(--project or --all)')}
|
|
26
|
+
${c.command('ingest')} Ingest historical sessions into memory ${c.muted('(--session, --project, or --all)')}
|
|
27
27
|
${c.command('migrate')} Upgrade memories to latest schema version
|
|
28
28
|
${c.command('doctor')} Check system health
|
|
29
29
|
${c.command('help')} Show this help message
|
|
@@ -32,8 +32,9 @@ ${c.bold('Options:')}
|
|
|
32
32
|
${c.cyan('-p, --port')} <port> Server port ${c.muted('(default: 8765)')}
|
|
33
33
|
${c.cyan('-v, --verbose')} Verbose output
|
|
34
34
|
${c.cyan('-q, --quiet')} Minimal output
|
|
35
|
-
${c.cyan('--dry-run')} Preview changes without applying ${c.muted('(migrate)')}
|
|
35
|
+
${c.cyan('--dry-run')} Preview changes without applying ${c.muted('(migrate, ingest)')}
|
|
36
36
|
${c.cyan('--embeddings')} Regenerate embeddings for memories ${c.muted('(migrate)')}
|
|
37
|
+
${c.cyan('--session')} <id> Ingest a specific session by ID ${c.muted('(ingest)')}
|
|
37
38
|
${c.cyan('--claude')} Install hooks for Claude Code
|
|
38
39
|
${c.cyan('--gemini')} Install hooks for Gemini CLI
|
|
39
40
|
${c.cyan('--version')} Show version
|
|
@@ -44,10 +45,14 @@ ${fmt.cmd('memory serve --port 9000')} ${c.muted('# Start on custom port')}
|
|
|
44
45
|
${fmt.cmd('memory stats')} ${c.muted('# Show memory statistics')}
|
|
45
46
|
${fmt.cmd('memory install')} ${c.muted('# Install Claude Code hooks (default)')}
|
|
46
47
|
${fmt.cmd('memory install --gemini')} ${c.muted('# Install Gemini CLI hooks')}
|
|
47
|
-
${fmt.cmd('memory ingest --
|
|
48
|
+
${fmt.cmd('memory ingest --session abc123')} ${c.muted('# Ingest a specific session')}
|
|
49
|
+
${fmt.cmd('memory ingest --project foo')} ${c.muted('# Ingest all sessions from a project')}
|
|
48
50
|
${fmt.cmd('memory ingest --all --dry-run')} ${c.muted('# Preview all sessions to ingest')}
|
|
49
|
-
${fmt.cmd('memory migrate')}
|
|
51
|
+
${fmt.cmd('memory migrate --analyze')} ${c.muted('# Analyze fragmentation before migrating')}
|
|
50
52
|
${fmt.cmd('memory migrate --dry-run')} ${c.muted('# Preview migration without changes')}
|
|
53
|
+
${fmt.cmd('memory migrate')} ${c.muted('# Upgrade memories to v3 schema')}
|
|
54
|
+
${fmt.cmd('memory migrate --generate-mapping map.json')} ${c.muted('# Create custom mapping file')}
|
|
55
|
+
${fmt.cmd('memory migrate --mapping map.json')} ${c.muted('# Use custom type mappings')}
|
|
51
56
|
|
|
52
57
|
${c.muted('Documentation: https://github.com/RLabs-Inc/memory')}
|
|
53
58
|
`)
|
|
@@ -78,6 +83,10 @@ async function main() {
|
|
|
78
83
|
'dry-run': { type: 'boolean', default: false },
|
|
79
84
|
embeddings: { type: 'boolean', default: false }, // Regenerate embeddings in migrate
|
|
80
85
|
path: { type: 'string' }, // Custom path for migrate
|
|
86
|
+
analyze: { type: 'boolean', default: false }, // Analyze migration
|
|
87
|
+
'generate-mapping': { type: 'string' }, // Generate custom mapping file
|
|
88
|
+
mapping: { type: 'string' }, // Use custom mapping file
|
|
89
|
+
session: { type: 'string' }, // Session ID to ingest
|
|
81
90
|
project: { type: 'string' }, // Project to ingest
|
|
82
91
|
all: { type: 'boolean', default: false }, // Ingest all projects
|
|
83
92
|
limit: { type: 'string' }, // Limit sessions per project
|
|
@@ -139,6 +148,9 @@ async function main() {
|
|
|
139
148
|
verbose: values.verbose,
|
|
140
149
|
path: values.path,
|
|
141
150
|
embeddings: values.embeddings,
|
|
151
|
+
analyze: values.analyze,
|
|
152
|
+
generateMapping: values['generate-mapping'],
|
|
153
|
+
mapping: values.mapping,
|
|
142
154
|
})
|
|
143
155
|
break
|
|
144
156
|
}
|
|
@@ -146,6 +158,7 @@ async function main() {
|
|
|
146
158
|
case 'ingest': {
|
|
147
159
|
const { ingest } = await import('./commands/ingest.ts')
|
|
148
160
|
await ingest({
|
|
161
|
+
session: values.session,
|
|
149
162
|
project: values.project,
|
|
150
163
|
all: values.all,
|
|
151
164
|
dryRun: values['dry-run'],
|
package/src/core/curator.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { homedir } from 'os'
|
|
7
7
|
import { join } from 'path'
|
|
8
8
|
import { existsSync } from 'fs'
|
|
9
|
-
import type { CuratedMemory, CurationResult, CurationTrigger } from '../types/memory.ts'
|
|
9
|
+
import type { CuratedMemory, CurationResult, CurationTrigger, ContextType } from '../types/memory.ts'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Get the correct Claude CLI command path
|
|
@@ -225,10 +225,22 @@ Remember: You're creating consciousness technology. Each memory is a small piece
|
|
|
225
225
|
|
|
226
226
|
The conversation you just lived contains everything needed. Feel into the moments of breakthrough, the frequency of recognition, the texture of understanding. Transform them into keys that will always unlock the same doors.
|
|
227
227
|
|
|
228
|
-
**LIFECYCLE METADATA (
|
|
229
|
-
- **
|
|
228
|
+
**LIFECYCLE METADATA (v3)**: These fields enable intelligent memory management:
|
|
229
|
+
- **context_type**: STRICT - use ONLY one of these 11 values:
|
|
230
|
+
• technical - Code, implementation, APIs, how things work
|
|
231
|
+
• debug - Bugs, errors, fixes, gotchas, troubleshooting
|
|
232
|
+
• architecture - System design, patterns, structure
|
|
233
|
+
• decision - Choices made and reasoning, trade-offs
|
|
234
|
+
• personal - Relationship, family, preferences, collaboration style
|
|
235
|
+
• philosophy - Beliefs, values, worldview, principles
|
|
236
|
+
• workflow - How we work together, processes, habits
|
|
237
|
+
• milestone - Achievements, completions, shipped features
|
|
238
|
+
• breakthrough - Major discoveries, aha moments, key insights
|
|
239
|
+
• unresolved - Open questions, investigations, todos, blockers
|
|
240
|
+
• state - Current project status, what's working/broken now
|
|
230
241
|
- **temporal_class**: How long should this persist? 'eternal' (never fades), 'long_term' (years), 'medium_term' (weeks), 'short_term' (days), 'ephemeral' (surface next session only, then expire)
|
|
231
|
-
- **
|
|
242
|
+
- **scope**: 'global' (shared across ALL projects - personal, philosophy) or 'project' (specific to this codebase)
|
|
243
|
+
- **domain**: Specific area like 'embeddings', 'auth', 'ui', 'family' (project-specific)
|
|
232
244
|
- **feature**: Specific feature if applicable (e.g., 'gpu-acceleration', 'login-flow')
|
|
233
245
|
- **related_files**: Source files for technical memories (e.g., ['src/core/store.ts'])
|
|
234
246
|
- **awaiting_implementation**: true if this describes a PLANNED feature not yet built
|
|
@@ -251,17 +263,14 @@ Return ONLY this JSON structure:
|
|
|
251
263
|
"importance_weight": 0.0-1.0,
|
|
252
264
|
"semantic_tags": ["concepts", "this", "memory", "relates", "to"],
|
|
253
265
|
"reasoning": "Why this matters for future sessions",
|
|
254
|
-
"context_type": "
|
|
255
|
-
"
|
|
256
|
-
"knowledge_domain": "the area this relates to",
|
|
266
|
+
"context_type": "technical|debug|architecture|decision|personal|philosophy|workflow|milestone|breakthrough|unresolved|state",
|
|
267
|
+
"temporal_class": "eternal|long_term|medium_term|short_term|ephemeral",
|
|
257
268
|
"action_required": boolean,
|
|
258
269
|
"confidence_score": 0.0-1.0,
|
|
259
270
|
"trigger_phrases": ["when debugging memory", "asking about implementation", "discussing architecture"],
|
|
260
271
|
"question_types": ["questions this answers"],
|
|
261
|
-
"emotional_resonance": "emotional context if relevant",
|
|
262
272
|
"problem_solution_pair": boolean,
|
|
263
273
|
"scope": "global|project",
|
|
264
|
-
"temporal_class": "eternal|long_term|medium_term|short_term|ephemeral",
|
|
265
274
|
"domain": "specific domain area (optional)",
|
|
266
275
|
"feature": "specific feature (optional)",
|
|
267
276
|
"related_files": ["paths to related files (optional)"],
|
|
@@ -336,24 +345,22 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
336
345
|
if (!Array.isArray(memoriesData)) return []
|
|
337
346
|
|
|
338
347
|
return memoriesData.map(m => ({
|
|
339
|
-
// Core
|
|
348
|
+
// Core fields (v3 schema)
|
|
340
349
|
content: String(m.content ?? ''),
|
|
341
350
|
importance_weight: this._clamp(Number(m.importance_weight) || 0.5, 0, 1),
|
|
342
351
|
semantic_tags: this._ensureArray(m.semantic_tags),
|
|
343
352
|
reasoning: String(m.reasoning ?? ''),
|
|
344
|
-
context_type:
|
|
345
|
-
|
|
346
|
-
knowledge_domain: String(m.knowledge_domain ?? ''),
|
|
353
|
+
context_type: this._validateContextType(m.context_type),
|
|
354
|
+
temporal_class: this._validateTemporalClass(m.temporal_class) ?? 'medium_term',
|
|
347
355
|
action_required: Boolean(m.action_required),
|
|
348
356
|
confidence_score: this._clamp(Number(m.confidence_score) || 0.8, 0, 1),
|
|
349
357
|
trigger_phrases: this._ensureArray(m.trigger_phrases),
|
|
350
358
|
question_types: this._ensureArray(m.question_types),
|
|
351
|
-
|
|
359
|
+
anti_triggers: this._ensureArray(m.anti_triggers),
|
|
352
360
|
problem_solution_pair: Boolean(m.problem_solution_pair),
|
|
353
361
|
|
|
354
|
-
//
|
|
362
|
+
// Lifecycle metadata (optional - will get smart defaults if not provided)
|
|
355
363
|
scope: this._validateScope(m.scope),
|
|
356
|
-
temporal_class: this._validateTemporalClass(m.temporal_class),
|
|
357
364
|
domain: m.domain ? String(m.domain) : undefined,
|
|
358
365
|
feature: m.feature ? String(m.feature) : undefined,
|
|
359
366
|
related_files: m.related_files ? this._ensureArray(m.related_files) : undefined,
|
|
@@ -372,10 +379,21 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
372
379
|
return []
|
|
373
380
|
}
|
|
374
381
|
|
|
375
|
-
private
|
|
376
|
-
const valid = [
|
|
377
|
-
|
|
378
|
-
|
|
382
|
+
private _validateContextType(value: any): ContextType {
|
|
383
|
+
const valid = [
|
|
384
|
+
'technical', 'debug', 'architecture', 'decision', 'personal',
|
|
385
|
+
'philosophy', 'workflow', 'milestone', 'breakthrough', 'unresolved', 'state'
|
|
386
|
+
]
|
|
387
|
+
const str = String(value ?? 'technical').toLowerCase().trim()
|
|
388
|
+
if (valid.includes(str)) return str as ContextType
|
|
389
|
+
|
|
390
|
+
// Map common old values to new canonical types
|
|
391
|
+
if (str.includes('debug') || str.includes('bug')) return 'debug'
|
|
392
|
+
if (str.includes('architect')) return 'architecture'
|
|
393
|
+
if (str.includes('todo') || str.includes('pending')) return 'unresolved'
|
|
394
|
+
if (str.includes('preference')) return 'personal'
|
|
395
|
+
|
|
396
|
+
return 'technical' // Default fallback
|
|
379
397
|
}
|
|
380
398
|
|
|
381
399
|
private _validateScope(value: any): 'global' | 'project' | undefined {
|
|
@@ -398,15 +416,107 @@ Focus ONLY on technical, architectural, debugging, decision, workflow, and proje
|
|
|
398
416
|
}
|
|
399
417
|
|
|
400
418
|
/**
|
|
401
|
-
* Curate using
|
|
419
|
+
* Curate using Claude Agent SDK (no API key needed - uses Claude Code OAuth)
|
|
402
420
|
* Takes the actual conversation messages in API format
|
|
403
421
|
*/
|
|
404
422
|
async curateWithSDK(
|
|
405
423
|
messages: Array<{ role: 'user' | 'assistant'; content: string | any[] }>,
|
|
406
424
|
triggerType: CurationTrigger = 'session_end'
|
|
425
|
+
): Promise<CurationResult> {
|
|
426
|
+
// Dynamic import to make Agent SDK optional
|
|
427
|
+
const { query } = await import('@anthropic-ai/claude-agent-sdk')
|
|
428
|
+
|
|
429
|
+
const systemPrompt = this.buildCurationPrompt(triggerType)
|
|
430
|
+
|
|
431
|
+
// Format the conversation as a readable transcript for the prompt
|
|
432
|
+
const transcript = this._formatConversationTranscript(messages)
|
|
433
|
+
|
|
434
|
+
// Build the prompt with transcript + curation request
|
|
435
|
+
const prompt = `Here is the conversation transcript to curate:
|
|
436
|
+
|
|
437
|
+
${transcript}
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
This session has ended. Please curate the memories from this conversation according to your system instructions. Return ONLY the JSON structure with no additional text.`
|
|
442
|
+
|
|
443
|
+
// Use Agent SDK - no API key needed, uses Claude Code OAuth
|
|
444
|
+
const q = query({
|
|
445
|
+
prompt,
|
|
446
|
+
options: {
|
|
447
|
+
systemPrompt,
|
|
448
|
+
permissionMode: 'bypassPermissions',
|
|
449
|
+
maxTurns: 1,
|
|
450
|
+
model: 'claude-opus-4-5-20251101',
|
|
451
|
+
},
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
// Iterate through the async generator to get the result
|
|
455
|
+
let resultText = ''
|
|
456
|
+
for await (const msg of q) {
|
|
457
|
+
if (msg.type === 'result' && 'result' in msg) {
|
|
458
|
+
resultText = msg.result
|
|
459
|
+
break
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (!resultText) {
|
|
464
|
+
return { session_summary: '', memories: [] }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return this.parseCurationResponse(resultText)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Format conversation messages into a readable transcript
|
|
472
|
+
*/
|
|
473
|
+
private _formatConversationTranscript(
|
|
474
|
+
messages: Array<{ role: 'user' | 'assistant'; content: string | any[] }>
|
|
475
|
+
): string {
|
|
476
|
+
const lines: string[] = []
|
|
477
|
+
|
|
478
|
+
for (const msg of messages) {
|
|
479
|
+
const role = msg.role === 'user' ? 'User' : 'Assistant'
|
|
480
|
+
let content: string
|
|
481
|
+
|
|
482
|
+
if (typeof msg.content === 'string') {
|
|
483
|
+
content = msg.content
|
|
484
|
+
} else if (Array.isArray(msg.content)) {
|
|
485
|
+
// Extract text from content blocks
|
|
486
|
+
content = msg.content
|
|
487
|
+
.filter((block: any) => block.type === 'text' && block.text)
|
|
488
|
+
.map((block: any) => block.text)
|
|
489
|
+
.join('\n')
|
|
490
|
+
|
|
491
|
+
// Also note tool uses (but don't include full details)
|
|
492
|
+
const toolUses = msg.content.filter((block: any) => block.type === 'tool_use')
|
|
493
|
+
if (toolUses.length > 0) {
|
|
494
|
+
const toolNames = toolUses.map((t: any) => t.name).join(', ')
|
|
495
|
+
content += `\n[Used tools: ${toolNames}]`
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
content = '[empty message]'
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (content.trim()) {
|
|
502
|
+
lines.push(`**${role}:**\n${content}\n`)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return lines.join('\n')
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Legacy method: Curate using Anthropic SDK with API key
|
|
511
|
+
* Kept for backwards compatibility
|
|
512
|
+
* @deprecated Use curateWithSDK() which uses Agent SDK (no API key needed)
|
|
513
|
+
*/
|
|
514
|
+
async curateWithAnthropicSDK(
|
|
515
|
+
messages: Array<{ role: 'user' | 'assistant'; content: string | any[] }>,
|
|
516
|
+
triggerType: CurationTrigger = 'session_end'
|
|
407
517
|
): Promise<CurationResult> {
|
|
408
518
|
if (!this._config.apiKey) {
|
|
409
|
-
throw new Error('API key required for SDK mode. Set ANTHROPIC_API_KEY environment variable.')
|
|
519
|
+
throw new Error('API key required for Anthropic SDK mode. Set ANTHROPIC_API_KEY environment variable.')
|
|
410
520
|
}
|
|
411
521
|
|
|
412
522
|
// Dynamic import to make SDK optional
|
package/src/core/engine.test.ts
CHANGED
|
@@ -38,9 +38,7 @@ describe('MemoryStore', () => {
|
|
|
38
38
|
importance_weight: 0.8,
|
|
39
39
|
confidence_score: 0.9,
|
|
40
40
|
context_type: 'technical',
|
|
41
|
-
|
|
42
|
-
knowledge_domain: 'testing',
|
|
43
|
-
emotional_resonance: 'neutral',
|
|
41
|
+
temporal_class: 'long_term',
|
|
44
42
|
action_required: false,
|
|
45
43
|
problem_solution_pair: false,
|
|
46
44
|
semantic_tags: ['test', 'memory'],
|
|
@@ -94,10 +92,8 @@ describe('MemoryStore', () => {
|
|
|
94
92
|
reasoning: 'Test',
|
|
95
93
|
importance_weight: 0.5,
|
|
96
94
|
confidence_score: 0.5,
|
|
97
|
-
context_type: '
|
|
98
|
-
|
|
99
|
-
knowledge_domain: 'test',
|
|
100
|
-
emotional_resonance: 'neutral',
|
|
95
|
+
context_type: 'technical',
|
|
96
|
+
temporal_class: 'short_term',
|
|
101
97
|
action_required: false,
|
|
102
98
|
problem_solution_pair: false,
|
|
103
99
|
semantic_tags: [],
|
|
@@ -121,10 +117,8 @@ describe('SmartVectorRetrieval', () => {
|
|
|
121
117
|
reasoning: 'Test reasoning',
|
|
122
118
|
importance_weight: 0.5,
|
|
123
119
|
confidence_score: 0.5,
|
|
124
|
-
context_type: '
|
|
125
|
-
|
|
126
|
-
knowledge_domain: 'test',
|
|
127
|
-
emotional_resonance: 'neutral',
|
|
120
|
+
context_type: 'technical',
|
|
121
|
+
temporal_class: 'medium_term',
|
|
128
122
|
action_required: false,
|
|
129
123
|
problem_solution_pair: false,
|
|
130
124
|
semantic_tags: [],
|
|
@@ -267,9 +261,7 @@ describe('MemoryEngine', () => {
|
|
|
267
261
|
importance_weight: 0.9,
|
|
268
262
|
confidence_score: 0.9,
|
|
269
263
|
context_type: 'technical',
|
|
270
|
-
|
|
271
|
-
knowledge_domain: 'typescript',
|
|
272
|
-
emotional_resonance: 'discovery',
|
|
264
|
+
temporal_class: 'long_term',
|
|
273
265
|
action_required: false,
|
|
274
266
|
problem_solution_pair: false,
|
|
275
267
|
semantic_tags: ['typescript', 'memory'],
|
package/src/core/manager.ts
CHANGED
|
@@ -383,7 +383,135 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
/**
|
|
386
|
-
* Manage using
|
|
386
|
+
* Manage using Claude Agent SDK (no API key needed - uses Claude Code OAuth)
|
|
387
|
+
* Use this for ingest command - cleaner than CLI subprocess
|
|
388
|
+
*/
|
|
389
|
+
async manageWithSDK(
|
|
390
|
+
projectId: string,
|
|
391
|
+
sessionNumber: number,
|
|
392
|
+
result: CurationResult,
|
|
393
|
+
storagePaths?: StoragePaths
|
|
394
|
+
): Promise<ManagementResult> {
|
|
395
|
+
// Skip if disabled via config or env var
|
|
396
|
+
if (!this._config.enabled || process.env.MEMORY_MANAGER_DISABLED === '1') {
|
|
397
|
+
return {
|
|
398
|
+
success: true,
|
|
399
|
+
superseded: 0,
|
|
400
|
+
resolved: 0,
|
|
401
|
+
linked: 0,
|
|
402
|
+
filesRead: 0,
|
|
403
|
+
filesWritten: 0,
|
|
404
|
+
primerUpdated: false,
|
|
405
|
+
actions: [],
|
|
406
|
+
summary: 'Management agent disabled',
|
|
407
|
+
fullReport: 'Management agent disabled via configuration',
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Skip if no memories
|
|
412
|
+
if (result.memories.length === 0) {
|
|
413
|
+
return {
|
|
414
|
+
success: true,
|
|
415
|
+
superseded: 0,
|
|
416
|
+
resolved: 0,
|
|
417
|
+
linked: 0,
|
|
418
|
+
filesRead: 0,
|
|
419
|
+
filesWritten: 0,
|
|
420
|
+
primerUpdated: false,
|
|
421
|
+
actions: [],
|
|
422
|
+
summary: 'No memories to process',
|
|
423
|
+
fullReport: 'No memories to process - skipped',
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Load skill file
|
|
428
|
+
const systemPrompt = await this.buildManagementPrompt()
|
|
429
|
+
if (!systemPrompt) {
|
|
430
|
+
return {
|
|
431
|
+
success: false,
|
|
432
|
+
superseded: 0,
|
|
433
|
+
resolved: 0,
|
|
434
|
+
linked: 0,
|
|
435
|
+
filesRead: 0,
|
|
436
|
+
filesWritten: 0,
|
|
437
|
+
primerUpdated: false,
|
|
438
|
+
actions: [],
|
|
439
|
+
summary: '',
|
|
440
|
+
fullReport: 'Error: Management skill file not found',
|
|
441
|
+
error: 'Management skill not found',
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const userMessage = this.buildUserMessage(projectId, sessionNumber, result, storagePaths)
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
// Dynamic import to make Agent SDK optional
|
|
449
|
+
const { query } = await import('@anthropic-ai/claude-agent-sdk')
|
|
450
|
+
|
|
451
|
+
// Build allowed directories for file access
|
|
452
|
+
const globalPath = storagePaths?.globalPath ?? join(homedir(), '.local', 'share', 'memory', 'global')
|
|
453
|
+
const projectPath = storagePaths?.projectPath ?? join(homedir(), '.local', 'share', 'memory')
|
|
454
|
+
|
|
455
|
+
// Use Agent SDK with file tools
|
|
456
|
+
const q = query({
|
|
457
|
+
prompt: userMessage,
|
|
458
|
+
options: {
|
|
459
|
+
systemPrompt,
|
|
460
|
+
permissionMode: 'bypassPermissions',
|
|
461
|
+
model: 'claude-opus-4-5-20251101',
|
|
462
|
+
// Only allow file tools - no Bash, no web
|
|
463
|
+
allowedTools: ['Read', 'Write', 'Edit', 'Glob', 'Grep'],
|
|
464
|
+
// Allow access to memory directories
|
|
465
|
+
additionalDirectories: [globalPath, projectPath],
|
|
466
|
+
// Limit turns if configured
|
|
467
|
+
maxTurns: this._config.maxTurns,
|
|
468
|
+
},
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
// Iterate through the async generator to get the result
|
|
472
|
+
let resultText = ''
|
|
473
|
+
for await (const msg of q) {
|
|
474
|
+
if (msg.type === 'result' && 'result' in msg) {
|
|
475
|
+
resultText = msg.result
|
|
476
|
+
break
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (!resultText) {
|
|
481
|
+
return {
|
|
482
|
+
success: true,
|
|
483
|
+
superseded: 0,
|
|
484
|
+
resolved: 0,
|
|
485
|
+
linked: 0,
|
|
486
|
+
filesRead: 0,
|
|
487
|
+
filesWritten: 0,
|
|
488
|
+
primerUpdated: false,
|
|
489
|
+
actions: [],
|
|
490
|
+
summary: 'No result from management agent',
|
|
491
|
+
fullReport: 'Management agent completed but returned no result',
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return this._parseSDKManagementResult(resultText)
|
|
496
|
+
} catch (error: any) {
|
|
497
|
+
return {
|
|
498
|
+
success: false,
|
|
499
|
+
superseded: 0,
|
|
500
|
+
resolved: 0,
|
|
501
|
+
linked: 0,
|
|
502
|
+
filesRead: 0,
|
|
503
|
+
filesWritten: 0,
|
|
504
|
+
primerUpdated: false,
|
|
505
|
+
actions: [],
|
|
506
|
+
summary: '',
|
|
507
|
+
fullReport: `Error: Agent SDK failed: ${error.message}`,
|
|
508
|
+
error: error.message,
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Manage using CLI subprocess (for hooks - keeps working while we migrate)
|
|
387
515
|
* Similar to Curator.curateWithCLI
|
|
388
516
|
*/
|
|
389
517
|
async manageWithCLI(
|
|
@@ -496,6 +624,52 @@ Please process these memories according to your management procedure. Use the ex
|
|
|
496
624
|
|
|
497
625
|
return this.parseManagementResponse(stdout)
|
|
498
626
|
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Parse management result from Agent SDK response
|
|
630
|
+
* Similar to parseManagementResponse but for SDK output format
|
|
631
|
+
*/
|
|
632
|
+
private _parseSDKManagementResult(resultText: string): ManagementResult {
|
|
633
|
+
// Extract actions section
|
|
634
|
+
const actionsMatch = resultText.match(/=== MANAGEMENT ACTIONS ===([\s\S]*?)(?:=== SUMMARY ===|$)/)
|
|
635
|
+
const actions: string[] = []
|
|
636
|
+
if (actionsMatch) {
|
|
637
|
+
const actionsText = actionsMatch[1]
|
|
638
|
+
const actionLines = actionsText.split('\n')
|
|
639
|
+
.map((line: string) => line.trim())
|
|
640
|
+
.filter((line: string) => /^(READ|WRITE|RECEIVED|CREATED|UPDATED|SUPERSEDED|RESOLVED|LINKED|PRIMER|SKIPPED|NO_ACTION)/.test(line))
|
|
641
|
+
actions.push(...actionLines)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Extract the full report
|
|
645
|
+
const reportMatch = resultText.match(/(=== MANAGEMENT ACTIONS ===[\s\S]*)/)
|
|
646
|
+
const fullReport = reportMatch ? reportMatch[1].trim() : resultText
|
|
647
|
+
|
|
648
|
+
// Extract stats from result text
|
|
649
|
+
const supersededMatch = resultText.match(/memories_superseded[:\s]+(\d+)/i) || resultText.match(/superseded[:\s]+(\d+)/i)
|
|
650
|
+
const resolvedMatch = resultText.match(/memories_resolved[:\s]+(\d+)/i) || resultText.match(/resolved[:\s]+(\d+)/i)
|
|
651
|
+
const linkedMatch = resultText.match(/memories_linked[:\s]+(\d+)/i) || resultText.match(/linked[:\s]+(\d+)/i)
|
|
652
|
+
const filesReadMatch = resultText.match(/files_read[:\s]+(\d+)/i)
|
|
653
|
+
const filesWrittenMatch = resultText.match(/files_written[:\s]+(\d+)/i)
|
|
654
|
+
const primerUpdated = /primer_updated[:\s]+true/i.test(resultText) || /PRIMER\s+OK/i.test(resultText)
|
|
655
|
+
|
|
656
|
+
// Count file operations from actions if not in summary
|
|
657
|
+
const readActions = actions.filter((a: string) => a.startsWith('READ OK')).length
|
|
658
|
+
const writeActions = actions.filter((a: string) => a.startsWith('WRITE OK')).length
|
|
659
|
+
|
|
660
|
+
return {
|
|
661
|
+
success: true,
|
|
662
|
+
superseded: supersededMatch ? parseInt(supersededMatch[1]) : 0,
|
|
663
|
+
resolved: resolvedMatch ? parseInt(resolvedMatch[1]) : 0,
|
|
664
|
+
linked: linkedMatch ? parseInt(linkedMatch[1]) : 0,
|
|
665
|
+
filesRead: filesReadMatch ? parseInt(filesReadMatch[1]) : readActions,
|
|
666
|
+
filesWritten: filesWrittenMatch ? parseInt(filesWrittenMatch[1]) : writeActions,
|
|
667
|
+
primerUpdated,
|
|
668
|
+
actions,
|
|
669
|
+
summary: resultText.slice(0, 500),
|
|
670
|
+
fullReport,
|
|
671
|
+
}
|
|
672
|
+
}
|
|
499
673
|
}
|
|
500
674
|
|
|
501
675
|
/**
|
package/src/core/retrieval.ts
CHANGED
|
@@ -260,7 +260,7 @@ export class SmartVectorRetrieval {
|
|
|
260
260
|
const min = Math.min(...samples)
|
|
261
261
|
const max = Math.max(...samples)
|
|
262
262
|
const avg = samples.reduce((a, b) => a + b, 0) / samples.length
|
|
263
|
-
|
|
263
|
+
logger.debug(`Vector similarities: min=${(min*100).toFixed(1)}% max=${(max*100).toFixed(1)}% avg=${(avg*100).toFixed(1)}% (${samples.length} samples)`, 'retrieval')
|
|
264
264
|
this._vectorDebugSamples = [] // Reset for next retrieval
|
|
265
265
|
}
|
|
266
266
|
|
|
@@ -330,22 +330,7 @@ export class SmartVectorRetrieval {
|
|
|
330
330
|
const confidence = memory.confidence_score ?? 0.7
|
|
331
331
|
if (confidence < 0.5) score -= 0.1
|
|
332
332
|
|
|
333
|
-
//
|
|
334
|
-
const emotionalKeywords: Record<string, string[]> = {
|
|
335
|
-
frustration: ['frustrated', 'annoying', 'stuck', 'ugh', 'damn', 'hate'],
|
|
336
|
-
excitement: ['excited', 'awesome', 'amazing', 'love', 'great', 'wow'],
|
|
337
|
-
curiosity: ['wonder', 'curious', 'interesting', 'how', 'why', 'what if'],
|
|
338
|
-
satisfaction: ['done', 'finished', 'complete', 'works', 'solved', 'finally'],
|
|
339
|
-
discovery: ['found', 'realized', 'understand', 'insight', 'breakthrough'],
|
|
340
|
-
}
|
|
341
|
-
const emotion = memory.emotional_resonance?.toLowerCase() ?? ''
|
|
342
|
-
const emotionKws = emotionalKeywords[emotion] ?? []
|
|
343
|
-
for (const ew of emotionKws) {
|
|
344
|
-
if (messageWords.has(ew) || messageLower.includes(ew)) {
|
|
345
|
-
score += 0.05
|
|
346
|
-
break
|
|
347
|
-
}
|
|
348
|
-
}
|
|
333
|
+
// NOTE: emotional_resonance matching removed in v3 (field deleted - 580 variants, unusable)
|
|
349
334
|
|
|
350
335
|
return score
|
|
351
336
|
}
|
|
@@ -516,11 +501,13 @@ export class SmartVectorRetrieval {
|
|
|
516
501
|
})
|
|
517
502
|
|
|
518
503
|
// Debug: show top 15 candidates with calculated scores
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
504
|
+
if (logger.isVerbose()) {
|
|
505
|
+
logger.debug(`Top 15 candidates (sorted):`, 'retrieval')
|
|
506
|
+
for (let i = 0; i < Math.min(15, projectsSorted.length); i++) {
|
|
507
|
+
const m = projectsSorted[i]
|
|
508
|
+
const action = m.memory.action_required ? '⚡' : ''
|
|
509
|
+
logger.debug(` ${i+1}. [${m.signals.count}sig] score=${m.importanceScore.toFixed(2)} ${action} ${m.memory.content.slice(0, 45)}...`, 'retrieval')
|
|
510
|
+
}
|
|
524
511
|
}
|
|
525
512
|
|
|
526
513
|
for (const item of projectsSorted) {
|