super-memory-pro 0.2.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 +247 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -0
- package/dist/db/connection.d.ts +20 -0
- package/dist/db/connection.d.ts.map +1 -0
- package/dist/db/connection.js +66 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/migrations/001_initial.sql +102 -0
- package/dist/db/migrations.d.ts +14 -0
- package/dist/db/migrations.d.ts.map +1 -0
- package/dist/db/migrations.js +86 -0
- package/dist/db/migrations.js.map +1 -0
- package/dist/db/queries.d.ts +24 -0
- package/dist/db/queries.d.ts.map +1 -0
- package/dist/db/queries.js +179 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/hooks/event-handler.d.ts +13 -0
- package/dist/hooks/event-handler.d.ts.map +1 -0
- package/dist/hooks/event-handler.js +80 -0
- package/dist/hooks/event-handler.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/message-handler.d.ts +13 -0
- package/dist/hooks/message-handler.d.ts.map +1 -0
- package/dist/hooks/message-handler.js +47 -0
- package/dist/hooks/message-handler.js.map +1 -0
- package/dist/hooks/session-compactor.d.ts +15 -0
- package/dist/hooks/session-compactor.d.ts.map +1 -0
- package/dist/hooks/session-compactor.js +40 -0
- package/dist/hooks/session-compactor.js.map +1 -0
- package/dist/hooks/system-transform.d.ts +27 -0
- package/dist/hooks/system-transform.d.ts.map +1 -0
- package/dist/hooks/system-transform.js +76 -0
- package/dist/hooks/system-transform.js.map +1 -0
- package/dist/hooks/tool-handler.d.ts +14 -0
- package/dist/hooks/tool-handler.d.ts.map +1 -0
- package/dist/hooks/tool-handler.js +94 -0
- package/dist/hooks/tool-handler.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +152 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/index.d.ts +19 -0
- package/dist/installer/index.d.ts.map +1 -0
- package/dist/installer/index.js +553 -0
- package/dist/installer/index.js.map +1 -0
- package/dist/memory/extractor.d.ts +29 -0
- package/dist/memory/extractor.d.ts.map +1 -0
- package/dist/memory/extractor.js +426 -0
- package/dist/memory/extractor.js.map +1 -0
- package/dist/memory/index.d.ts +7 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/processor.d.ts +50 -0
- package/dist/memory/processor.d.ts.map +1 -0
- package/dist/memory/processor.js +199 -0
- package/dist/memory/processor.js.map +1 -0
- package/dist/memory/search.d.ts +35 -0
- package/dist/memory/search.d.ts.map +1 -0
- package/dist/memory/search.js +170 -0
- package/dist/memory/search.js.map +1 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +112 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/server/index.d.ts +16 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +49 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware/async-handler.d.ts +11 -0
- package/dist/server/middleware/async-handler.d.ts.map +1 -0
- package/dist/server/middleware/async-handler.js +14 -0
- package/dist/server/middleware/async-handler.js.map +1 -0
- package/dist/server/middleware/error-handler.d.ts +11 -0
- package/dist/server/middleware/error-handler.d.ts.map +1 -0
- package/dist/server/middleware/error-handler.js +18 -0
- package/dist/server/middleware/error-handler.js.map +1 -0
- package/dist/server/routes/health.d.ts +2 -0
- package/dist/server/routes/health.d.ts.map +1 -0
- package/dist/server/routes/health.js +25 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/memory.d.ts +2 -0
- package/dist/server/routes/memory.d.ts.map +1 -0
- package/dist/server/routes/memory.js +139 -0
- package/dist/server/routes/memory.js.map +1 -0
- package/dist/server/schemas.d.ts +51 -0
- package/dist/server/schemas.d.ts.map +1 -0
- package/dist/server/schemas.js +23 -0
- package/dist/server/schemas.js.map +1 -0
- package/dist/tools/definitions.d.ts +130 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +78 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/index.d.ts +32 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +148 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// ─── Memory Processor ──────────────────────────────────────────────────────
|
|
2
|
+
// Orchestrator that combines extraction, storage, and search.
|
|
3
|
+
// This is the primary entry point for the memory system.
|
|
4
|
+
import { extractTechStack, extractDecisions, extractUserPreferences, extractPatterns, classifyContent, } from './extractor.js';
|
|
5
|
+
import { storeMemories } from './store.js';
|
|
6
|
+
import { searchRelevantMemories, formatMemoriesForPrompt } from './search.js';
|
|
7
|
+
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
8
|
+
const DEFAULT_MAX_INJECTIONS = 5;
|
|
9
|
+
/**
|
|
10
|
+
* Process a message: extract patterns → classify → store memories.
|
|
11
|
+
*
|
|
12
|
+
* This is the main pipeline for ingesting a message into the memory system.
|
|
13
|
+
* It runs all extractors in parallel, classifies the content, and persists
|
|
14
|
+
* everything atomically (each memory independently — partial failures are
|
|
15
|
+
* tolerated).
|
|
16
|
+
*
|
|
17
|
+
* Extraction steps:
|
|
18
|
+
* 1. Tech stack mentions → tech_stack memories
|
|
19
|
+
* 2. Decisions → decision memories
|
|
20
|
+
* 3. User preferences → user_profile memories
|
|
21
|
+
* 4. Workflow patterns → pattern memories
|
|
22
|
+
* 5. Content classification → appropriate type memory
|
|
23
|
+
*
|
|
24
|
+
* @param content - The message text to process
|
|
25
|
+
* @param metadata - Session / project / source context
|
|
26
|
+
* @returns - All memories created or updated
|
|
27
|
+
*/
|
|
28
|
+
export async function processMessage(content, metadata) {
|
|
29
|
+
if (!content || content.trim().length === 0) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const { sessionId, projectId, source } = metadata;
|
|
33
|
+
try {
|
|
34
|
+
// Step 1: Run all extractors
|
|
35
|
+
const [techStack, decisions, preferences, patterns] = await Promise.all([
|
|
36
|
+
safeExtract(() => extractTechStack(content)),
|
|
37
|
+
safeExtract(() => extractDecisions(content)),
|
|
38
|
+
safeExtract(() => extractUserPreferences(content)),
|
|
39
|
+
safeExtract(() => extractPatterns(content)),
|
|
40
|
+
]);
|
|
41
|
+
// Step 2: Classify content
|
|
42
|
+
const memoryType = classifyContent(content);
|
|
43
|
+
// Step 3: Build memory payloads
|
|
44
|
+
const memoryPayloads = [];
|
|
45
|
+
// 3a. Tech stack
|
|
46
|
+
if (techStack.length > 0) {
|
|
47
|
+
for (const tech of techStack) {
|
|
48
|
+
memoryPayloads.push({
|
|
49
|
+
memory_type: 'tech_stack',
|
|
50
|
+
project_id: projectId ?? null,
|
|
51
|
+
session_id: sessionId ?? null,
|
|
52
|
+
key: `tech:${tech.toLowerCase().replace(/[^a-z0-9.#]/g, '_')}`,
|
|
53
|
+
content: `Project uses ${tech}`,
|
|
54
|
+
importance: 4,
|
|
55
|
+
source: source ?? 'processor',
|
|
56
|
+
metadata: { technology: tech, extracted: true },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// 3b. Decisions
|
|
61
|
+
for (const d of decisions) {
|
|
62
|
+
memoryPayloads.push({
|
|
63
|
+
memory_type: 'decision',
|
|
64
|
+
project_id: projectId ?? null,
|
|
65
|
+
session_id: sessionId ?? null,
|
|
66
|
+
key: `decision:${normalizeKeyFragment(d.decision)}`,
|
|
67
|
+
content: d.decision,
|
|
68
|
+
importance: 4,
|
|
69
|
+
source: source ?? 'processor',
|
|
70
|
+
metadata: { context: d.context, extracted: true },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// 3c. Preferences
|
|
74
|
+
for (const pref of preferences) {
|
|
75
|
+
memoryPayloads.push({
|
|
76
|
+
memory_type: 'user_profile',
|
|
77
|
+
project_id: projectId ?? null,
|
|
78
|
+
session_id: sessionId ?? null,
|
|
79
|
+
key: `preference:${normalizeKeyFragment(pref)}`,
|
|
80
|
+
content: pref,
|
|
81
|
+
importance: 3,
|
|
82
|
+
source: source ?? 'processor',
|
|
83
|
+
metadata: { extracted: true },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// 3d. Patterns
|
|
87
|
+
for (const pat of patterns) {
|
|
88
|
+
memoryPayloads.push({
|
|
89
|
+
memory_type: 'pattern',
|
|
90
|
+
project_id: projectId ?? null,
|
|
91
|
+
session_id: sessionId ?? null,
|
|
92
|
+
key: `pattern:${normalizeKeyFragment(pat)}`,
|
|
93
|
+
content: pat,
|
|
94
|
+
importance: 2,
|
|
95
|
+
source: source ?? 'processor',
|
|
96
|
+
metadata: { extracted: true },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// 3e. Classified content — store the whole message
|
|
100
|
+
if (content.trim().length > 10) {
|
|
101
|
+
memoryPayloads.push({
|
|
102
|
+
memory_type: memoryType,
|
|
103
|
+
project_id: projectId ?? null,
|
|
104
|
+
session_id: sessionId ?? null,
|
|
105
|
+
key: `msg:${memoryType}:${createContentHash(content)}`,
|
|
106
|
+
content: content.trim(),
|
|
107
|
+
importance: 3,
|
|
108
|
+
source: source ?? 'processor',
|
|
109
|
+
metadata: {
|
|
110
|
+
classified_as: memoryType,
|
|
111
|
+
tech_stack: techStack,
|
|
112
|
+
decisions_count: decisions.length,
|
|
113
|
+
preferences_count: preferences.length,
|
|
114
|
+
patterns_count: patterns.length,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Step 4: Store all memories (parallel upsert)
|
|
119
|
+
if (memoryPayloads.length === 0)
|
|
120
|
+
return [];
|
|
121
|
+
return storeMemories(memoryPayloads);
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
console.error('[UltraMemory] processMessage: unexpected error', err);
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get relevant context for a query as formatted prompt text.
|
|
130
|
+
*
|
|
131
|
+
* Searches the memory store for relevant records, formats them as
|
|
132
|
+
* structured markdown, and returns a string suitable for injection
|
|
133
|
+
* into an LLM system prompt.
|
|
134
|
+
*
|
|
135
|
+
* @param query - The query to find context for
|
|
136
|
+
* @param options - Optional filters and limits
|
|
137
|
+
* @returns - Formatted context string (empty if nothing relevant)
|
|
138
|
+
*/
|
|
139
|
+
export async function getContextForQuery(query, options) {
|
|
140
|
+
if (!query || query.trim().length === 0) {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
const maxInjections = options?.maxInjections ?? DEFAULT_MAX_INJECTIONS;
|
|
144
|
+
try {
|
|
145
|
+
const results = await searchRelevantMemories(query, {
|
|
146
|
+
types: options?.types,
|
|
147
|
+
projectId: options?.projectId,
|
|
148
|
+
limit: maxInjections,
|
|
149
|
+
minImportance: options?.minImportance,
|
|
150
|
+
});
|
|
151
|
+
if (results.length === 0)
|
|
152
|
+
return '';
|
|
153
|
+
return formatMemoriesForPrompt(results);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
console.error('[UltraMemory] getContextForQuery: search failed', err);
|
|
157
|
+
return '';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// ─── Internal Helpers ──────────────────────────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Safely run an extractor; returns empty result on failure.
|
|
163
|
+
*/
|
|
164
|
+
function safeExtract(fn) {
|
|
165
|
+
try {
|
|
166
|
+
return fn();
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
console.error('[UltraMemory] extractor failed:', err);
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Create a stable content hash for deduplication keys.
|
|
175
|
+
* Uses a simple djb2 hash — not cryptographic, just good enough
|
|
176
|
+
* for stable key generation.
|
|
177
|
+
*/
|
|
178
|
+
function createContentHash(content) {
|
|
179
|
+
let hash = 5381;
|
|
180
|
+
const normalized = content.toLowerCase().trim();
|
|
181
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
182
|
+
hash = ((hash << 5) + hash + normalized.charCodeAt(i)) & 0xffffffff;
|
|
183
|
+
}
|
|
184
|
+
return Math.abs(hash).toString(36);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Normalize a text fragment into a stable key suffix.
|
|
188
|
+
* Lowercases, collapses whitespace, strips special characters,
|
|
189
|
+
* and truncates to 60 chars.
|
|
190
|
+
*/
|
|
191
|
+
function normalizeKeyFragment(text) {
|
|
192
|
+
return text
|
|
193
|
+
.toLowerCase()
|
|
194
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
195
|
+
.replace(/\s+/g, '_')
|
|
196
|
+
.replace(/^_+|_+$/g, '')
|
|
197
|
+
.slice(0, 60);
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor.js","sourceRoot":"","sources":["../../src/memory/processor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { MemorySearchResult, MemoryType } from '../types.js';
|
|
2
|
+
export interface SearchOptions {
|
|
3
|
+
/** Filter by specific memory types */
|
|
4
|
+
types?: MemoryType[];
|
|
5
|
+
/** Scope to a specific project */
|
|
6
|
+
projectId?: string;
|
|
7
|
+
/** Maximum results to return (default: 10) */
|
|
8
|
+
limit?: number;
|
|
9
|
+
/** Minimum importance threshold (1-5, default: 1) */
|
|
10
|
+
minImportance?: number;
|
|
11
|
+
/** Minimum relevance score threshold (0.0-1.0, default: 0) */
|
|
12
|
+
minRelevance?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Search for memories relevant to the given query.
|
|
16
|
+
*
|
|
17
|
+
* Delegates to the DB full-text search engine and layers on client-side
|
|
18
|
+
* importance/relevance filtering. Tracks access for LRU eviction.
|
|
19
|
+
*
|
|
20
|
+
* @param query - Natural-language search text
|
|
21
|
+
* @param options - Optional filters and limits
|
|
22
|
+
* @returns - Ranked memory search results
|
|
23
|
+
*/
|
|
24
|
+
export declare function searchRelevantMemories(query: string, options?: SearchOptions): Promise<MemorySearchResult[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Format memories as structured text for system prompt injection.
|
|
27
|
+
*
|
|
28
|
+
* Produces clean, hierarchical markdown grouped by memory type.
|
|
29
|
+
* Each entry includes its importance level and key content.
|
|
30
|
+
*
|
|
31
|
+
* @param memories - Search results to format
|
|
32
|
+
* @returns - Formatted markdown string (empty string if no memories)
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatMemoriesForPrompt(memories: MemorySearchResult[]): string;
|
|
35
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/memory/search.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKlE,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAiC/B;AA0DD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAqD9E"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// ─── Memory Search & Prompt Formatting ─────────────────────────────────────
|
|
2
|
+
// Retrieves relevant memories and formats them for LLM prompt injection.
|
|
3
|
+
import { searchMemories, recordMemoryAccess } from '../db/queries.js';
|
|
4
|
+
/**
|
|
5
|
+
* Search for memories relevant to the given query.
|
|
6
|
+
*
|
|
7
|
+
* Delegates to the DB full-text search engine and layers on client-side
|
|
8
|
+
* importance/relevance filtering. Tracks access for LRU eviction.
|
|
9
|
+
*
|
|
10
|
+
* @param query - Natural-language search text
|
|
11
|
+
* @param options - Optional filters and limits
|
|
12
|
+
* @returns - Ranked memory search results
|
|
13
|
+
*/
|
|
14
|
+
export async function searchRelevantMemories(query, options) {
|
|
15
|
+
if (!query || query.trim().length === 0) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const results = await searchMemories({
|
|
19
|
+
query: query.trim(),
|
|
20
|
+
types: options?.types,
|
|
21
|
+
project_id: options?.projectId,
|
|
22
|
+
limit: (options?.limit ?? 10) * 2, // Fetch extra for post-filtering
|
|
23
|
+
offset: 0,
|
|
24
|
+
});
|
|
25
|
+
// Apply client-side filters
|
|
26
|
+
let filtered = results;
|
|
27
|
+
if (options?.minImportance !== undefined && options.minImportance > 1) {
|
|
28
|
+
filtered = filtered.filter((r) => r.memory.importance >= options.minImportance);
|
|
29
|
+
}
|
|
30
|
+
if (options?.minRelevance !== undefined && options.minRelevance > 0) {
|
|
31
|
+
filtered = filtered.filter((r) => r.relevance >= options.minRelevance);
|
|
32
|
+
}
|
|
33
|
+
// Respect limit exactly
|
|
34
|
+
const limited = filtered.slice(0, options?.limit ?? 10);
|
|
35
|
+
// Track access for LRU without blocking the response
|
|
36
|
+
if (limited.length > 0) {
|
|
37
|
+
trackMemoryAccess(limited).catch(() => { });
|
|
38
|
+
}
|
|
39
|
+
return limited;
|
|
40
|
+
}
|
|
41
|
+
function groupByType(memories) {
|
|
42
|
+
const grouped = {
|
|
43
|
+
tech_stack: [],
|
|
44
|
+
decision: [],
|
|
45
|
+
user_profile: [],
|
|
46
|
+
pattern: [],
|
|
47
|
+
project_context: [],
|
|
48
|
+
explicit: [],
|
|
49
|
+
session: [],
|
|
50
|
+
other: [],
|
|
51
|
+
};
|
|
52
|
+
for (const m of memories) {
|
|
53
|
+
switch (m.memory.memory_type) {
|
|
54
|
+
case 'tech_stack':
|
|
55
|
+
grouped.tech_stack.push(m);
|
|
56
|
+
break;
|
|
57
|
+
case 'decision':
|
|
58
|
+
grouped.decision.push(m);
|
|
59
|
+
break;
|
|
60
|
+
case 'user_profile':
|
|
61
|
+
grouped.user_profile.push(m);
|
|
62
|
+
break;
|
|
63
|
+
case 'pattern':
|
|
64
|
+
grouped.pattern.push(m);
|
|
65
|
+
break;
|
|
66
|
+
case 'project_context':
|
|
67
|
+
grouped.project_context.push(m);
|
|
68
|
+
break;
|
|
69
|
+
case 'explicit':
|
|
70
|
+
grouped.explicit.push(m);
|
|
71
|
+
break;
|
|
72
|
+
case 'session':
|
|
73
|
+
grouped.session.push(m);
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
grouped.other.push(m);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return grouped;
|
|
81
|
+
}
|
|
82
|
+
/** Type label for display in prompt injection text. */
|
|
83
|
+
const TYPE_LABELS = {
|
|
84
|
+
tech_stack: 'Tech Stack',
|
|
85
|
+
decision: 'Decisions',
|
|
86
|
+
user_profile: 'User Preferences',
|
|
87
|
+
pattern: 'Workflow Patterns',
|
|
88
|
+
project_context: 'Project Context',
|
|
89
|
+
explicit: 'Saved Notes',
|
|
90
|
+
session: 'Session History',
|
|
91
|
+
other: 'Other Context',
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Format memories as structured text for system prompt injection.
|
|
95
|
+
*
|
|
96
|
+
* Produces clean, hierarchical markdown grouped by memory type.
|
|
97
|
+
* Each entry includes its importance level and key content.
|
|
98
|
+
*
|
|
99
|
+
* @param memories - Search results to format
|
|
100
|
+
* @returns - Formatted markdown string (empty string if no memories)
|
|
101
|
+
*/
|
|
102
|
+
export function formatMemoriesForPrompt(memories) {
|
|
103
|
+
if (memories.length === 0)
|
|
104
|
+
return '';
|
|
105
|
+
const grouped = groupByType(memories);
|
|
106
|
+
const sections = ['## Relevant Context'];
|
|
107
|
+
// Ordered groups by relevance/importance
|
|
108
|
+
const groupOrder = [
|
|
109
|
+
'explicit',
|
|
110
|
+
'tech_stack',
|
|
111
|
+
'decision',
|
|
112
|
+
'user_profile',
|
|
113
|
+
'project_context',
|
|
114
|
+
'pattern',
|
|
115
|
+
'session',
|
|
116
|
+
'other',
|
|
117
|
+
];
|
|
118
|
+
for (const key of groupOrder) {
|
|
119
|
+
const items = grouped[key];
|
|
120
|
+
if (items.length === 0)
|
|
121
|
+
continue;
|
|
122
|
+
const label = TYPE_LABELS[key] ?? key;
|
|
123
|
+
sections.push('');
|
|
124
|
+
sections.push(`### ${label}`);
|
|
125
|
+
if (key === 'tech_stack') {
|
|
126
|
+
// Compact inline format for tech
|
|
127
|
+
const techList = items
|
|
128
|
+
.map((r) => r.memory.content || r.memory.key.replace(/^tech:/, ''))
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
sections.push(techList.map((t) => `- ${t}`).join('\n'));
|
|
131
|
+
}
|
|
132
|
+
else if (key === 'decision') {
|
|
133
|
+
for (const item of items) {
|
|
134
|
+
const mem = item.memory;
|
|
135
|
+
const imp = importanceBadge(mem.importance);
|
|
136
|
+
const val = mem.value;
|
|
137
|
+
const ctx = val?.context;
|
|
138
|
+
sections.push(`- ${mem.content}${imp}`);
|
|
139
|
+
if (ctx) {
|
|
140
|
+
sections.push(` - Context: ${ctx}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
for (const item of items) {
|
|
146
|
+
const mem = item.memory;
|
|
147
|
+
const imp = importanceBadge(mem.importance);
|
|
148
|
+
sections.push(`- ${mem.content}${imp}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return sections.join('\n');
|
|
153
|
+
}
|
|
154
|
+
// ─── Internal Helpers ──────────────────────────────────────────────────────
|
|
155
|
+
/** Format importance as a human-readable badge. */
|
|
156
|
+
function importanceBadge(level) {
|
|
157
|
+
if (level >= 5)
|
|
158
|
+
return ' *(high importance)*';
|
|
159
|
+
if (level >= 4)
|
|
160
|
+
return '';
|
|
161
|
+
if (level <= 2)
|
|
162
|
+
return ' *(minor)*';
|
|
163
|
+
return '';
|
|
164
|
+
}
|
|
165
|
+
/** Non-blocking access tracking for LRU. */
|
|
166
|
+
async function trackMemoryAccess(results) {
|
|
167
|
+
const ids = results.map((r) => r.memory.id);
|
|
168
|
+
await Promise.allSettled(ids.map((id) => recordMemoryAccess(id)));
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/memory/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type pg from 'pg';
|
|
2
|
+
import type { MemoryCreate, MemoryRecord } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Store a memory record with automatic deduplication.
|
|
5
|
+
*
|
|
6
|
+
* Uses `createMemory` from queries.ts which performs an upsert on
|
|
7
|
+
* `(memory_type, project_id, key)`. When no explicit `key` is given,
|
|
8
|
+
* one is generated from the content to enable stable deduplication.
|
|
9
|
+
*
|
|
10
|
+
* @param data - Memory data to persist
|
|
11
|
+
* @param pool - Optional pg.Pool for transaction support
|
|
12
|
+
* @returns - The stored/updated MemoryRecord
|
|
13
|
+
*/
|
|
14
|
+
export declare function storeMemory(data: MemoryCreate, pool?: pg.Pool): Promise<MemoryRecord>;
|
|
15
|
+
/**
|
|
16
|
+
* Store multiple memory records in parallel.
|
|
17
|
+
* Each entry is independently upserted — a failure in one does not
|
|
18
|
+
* affect the others.
|
|
19
|
+
*
|
|
20
|
+
* @param data - Array of memory data to persist
|
|
21
|
+
* @returns - Array of stored MemoryRecords
|
|
22
|
+
*/
|
|
23
|
+
export declare function storeMemories(data: MemoryCreate[]): Promise<MemoryRecord[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Convenience: store tech-stack entries with the correct type and key prefix.
|
|
26
|
+
*/
|
|
27
|
+
export declare function storeTechStack(technologies: string[], metadata: {
|
|
28
|
+
projectId?: string;
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
source?: string;
|
|
31
|
+
}): Promise<MemoryRecord[]>;
|
|
32
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/memory/store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAc,MAAM,aAAa,CAAC;AAgC1E;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,YAAY,EAClB,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,GACb,OAAO,CAAC,YAAY,CAAC,CAsCvB;AAID;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAejF;AAID;;GAEG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE,OAAO,CAAC,YAAY,EAAE,CAAC,CAezB"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// ─── Memory Storage ────────────────────────────────────────────────────────
|
|
2
|
+
// Stores extracted memories with deduplication via upsert.
|
|
3
|
+
import { createMemory } from '../db/queries.js';
|
|
4
|
+
// ─── Key Generation ────────────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* Generate a stable deduplication key from content.
|
|
7
|
+
* Keys are normalised so identical or semantically similar content
|
|
8
|
+
* produces the same key, enabling upsert deduplication.
|
|
9
|
+
*/
|
|
10
|
+
function generateKey(content, type) {
|
|
11
|
+
const prefix = type ? `${type}:` : '';
|
|
12
|
+
// Lowercase, collapse whitespace, strip special chars
|
|
13
|
+
const normalized = content
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
16
|
+
.replace(/\s+/g, '_')
|
|
17
|
+
.replace(/^_+|_+$/g, '')
|
|
18
|
+
.slice(0, 80);
|
|
19
|
+
if (!normalized) {
|
|
20
|
+
// Fallback: use a short timestamp-based key
|
|
21
|
+
return `${prefix}entry_${Date.now()}`;
|
|
22
|
+
}
|
|
23
|
+
return `${prefix}${normalized}`;
|
|
24
|
+
}
|
|
25
|
+
// ─── Single Store ──────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Store a memory record with automatic deduplication.
|
|
28
|
+
*
|
|
29
|
+
* Uses `createMemory` from queries.ts which performs an upsert on
|
|
30
|
+
* `(memory_type, project_id, key)`. When no explicit `key` is given,
|
|
31
|
+
* one is generated from the content to enable stable deduplication.
|
|
32
|
+
*
|
|
33
|
+
* @param data - Memory data to persist
|
|
34
|
+
* @param pool - Optional pg.Pool for transaction support
|
|
35
|
+
* @returns - The stored/updated MemoryRecord
|
|
36
|
+
*/
|
|
37
|
+
export async function storeMemory(data, pool) {
|
|
38
|
+
// Ensure required fields
|
|
39
|
+
const memoryType = data.memory_type;
|
|
40
|
+
const key = data.key?.trim() || generateKey(data.content, memoryType);
|
|
41
|
+
// If a pool was provided, use it directly; otherwise queries.ts manages
|
|
42
|
+
// its own pool via getPool().
|
|
43
|
+
if (pool) {
|
|
44
|
+
const result = await pool.query(`INSERT INTO memories (memory_type, project_id, session_id, key, value, content, importance, source, metadata)
|
|
45
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
46
|
+
ON CONFLICT (memory_type, project_id, key)
|
|
47
|
+
DO UPDATE SET
|
|
48
|
+
content = EXCLUDED.content,
|
|
49
|
+
importance = GREATEST(memories.importance, EXCLUDED.importance),
|
|
50
|
+
value = memories.value || EXCLUDED.value,
|
|
51
|
+
updated_at = NOW()
|
|
52
|
+
RETURNING *`, [
|
|
53
|
+
memoryType,
|
|
54
|
+
data.project_id ?? null,
|
|
55
|
+
data.session_id ?? null,
|
|
56
|
+
key,
|
|
57
|
+
JSON.stringify(data.value ?? {}),
|
|
58
|
+
data.content,
|
|
59
|
+
data.importance ?? 3,
|
|
60
|
+
data.source ?? 'auto',
|
|
61
|
+
JSON.stringify(data.metadata ?? {}),
|
|
62
|
+
]);
|
|
63
|
+
return result.rows[0];
|
|
64
|
+
}
|
|
65
|
+
// No pool provided — delegate to queries.ts
|
|
66
|
+
return createMemory({
|
|
67
|
+
...data,
|
|
68
|
+
key,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// ─── Batch Store ───────────────────────────────────────────────────────────
|
|
72
|
+
/**
|
|
73
|
+
* Store multiple memory records in parallel.
|
|
74
|
+
* Each entry is independently upserted — a failure in one does not
|
|
75
|
+
* affect the others.
|
|
76
|
+
*
|
|
77
|
+
* @param data - Array of memory data to persist
|
|
78
|
+
* @returns - Array of stored MemoryRecords
|
|
79
|
+
*/
|
|
80
|
+
export async function storeMemories(data) {
|
|
81
|
+
if (data.length === 0)
|
|
82
|
+
return [];
|
|
83
|
+
const results = await Promise.allSettled(data.map((d) => storeMemory(d)));
|
|
84
|
+
return results.reduce((acc, r) => {
|
|
85
|
+
if (r.status === 'fulfilled') {
|
|
86
|
+
acc.push(r.value);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.error('[UltraMemory] storeMemories: failed to store memory', r.reason);
|
|
90
|
+
}
|
|
91
|
+
return acc;
|
|
92
|
+
}, []);
|
|
93
|
+
}
|
|
94
|
+
// ─── Tech-Stack Specific Store ─────────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Convenience: store tech-stack entries with the correct type and key prefix.
|
|
97
|
+
*/
|
|
98
|
+
export async function storeTechStack(technologies, metadata) {
|
|
99
|
+
if (technologies.length === 0)
|
|
100
|
+
return [];
|
|
101
|
+
return storeMemories(technologies.map((tech) => ({
|
|
102
|
+
memory_type: 'tech_stack',
|
|
103
|
+
project_id: metadata.projectId ?? null,
|
|
104
|
+
session_id: metadata.sessionId ?? null,
|
|
105
|
+
key: `tech:${tech.toLowerCase().replace(/[^a-z0-9.#]/g, '_')}`,
|
|
106
|
+
content: `Project uses ${tech}`,
|
|
107
|
+
importance: 4,
|
|
108
|
+
source: metadata.source ?? 'extractor',
|
|
109
|
+
metadata: { technology: tech, extracted: true },
|
|
110
|
+
})));
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/memory/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Express } from 'express';
|
|
2
|
+
import type { PluginConfig } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create and start the Ultra Memory HTTP API server.
|
|
5
|
+
*
|
|
6
|
+
* @param config - Plugin configuration (DB, server host/port, etc.)
|
|
7
|
+
* @returns `{ app, url, close }` — the Express app, its listening URL,
|
|
8
|
+
* and an async `close()` function for graceful shutdown.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createServer(config: PluginConfig): Promise<{
|
|
11
|
+
app: Express;
|
|
12
|
+
url: string;
|
|
13
|
+
close: () => Promise<void>;
|
|
14
|
+
}>;
|
|
15
|
+
export type { CreateMemoryInput, SearchQueryInput, UpdateMemoryInput } from './schemas.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CA4CpE;AAGD,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { getPool, closePool } from '../db/connection.js';
|
|
3
|
+
import { healthRouter } from './routes/health.js';
|
|
4
|
+
import { memoryRouter } from './routes/memory.js';
|
|
5
|
+
import { errorHandler } from './middleware/error-handler.js';
|
|
6
|
+
/**
|
|
7
|
+
* Create and start the Ultra Memory HTTP API server.
|
|
8
|
+
*
|
|
9
|
+
* @param config - Plugin configuration (DB, server host/port, etc.)
|
|
10
|
+
* @returns `{ app, url, close }` — the Express app, its listening URL,
|
|
11
|
+
* and an async `close()` function for graceful shutdown.
|
|
12
|
+
*/
|
|
13
|
+
export async function createServer(config) {
|
|
14
|
+
// Initialise the database connection pool before accepting requests
|
|
15
|
+
getPool(config);
|
|
16
|
+
const app = express();
|
|
17
|
+
// ── Global middleware ────────────────────────────────────────────────
|
|
18
|
+
app.use(express.json());
|
|
19
|
+
// ── Routes ──────────────────────────────────────────────────────────
|
|
20
|
+
app.use('/', healthRouter);
|
|
21
|
+
app.use('/api', memoryRouter);
|
|
22
|
+
// ── Error handler (must be last) ────────────────────────────────────
|
|
23
|
+
app.use(errorHandler);
|
|
24
|
+
// ── Start listening ─────────────────────────────────────────────────
|
|
25
|
+
const host = config.server.host;
|
|
26
|
+
const port = config.server.port;
|
|
27
|
+
let server;
|
|
28
|
+
const url = await new Promise((resolve, reject) => {
|
|
29
|
+
server = app.listen(port, host, () => {
|
|
30
|
+
const addr = server.address();
|
|
31
|
+
const actualPort = addr && typeof addr === 'object' ? addr.port : port;
|
|
32
|
+
const serverUrl = `http://${host}:${actualPort}`;
|
|
33
|
+
console.log(`[UltraMemory] Memory server listening on ${serverUrl}`);
|
|
34
|
+
resolve(serverUrl);
|
|
35
|
+
});
|
|
36
|
+
server.on('error', (err) => {
|
|
37
|
+
reject(err);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
app,
|
|
42
|
+
url,
|
|
43
|
+
close: async () => {
|
|
44
|
+
server.close();
|
|
45
|
+
await closePool();
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAyB,MAAM,SAAS,CAAC;AAGhD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAoB;IAEpB,oEAAoE;IACpE,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhB,MAAM,GAAG,GAAY,OAAO,EAAE,CAAC;IAE/B,wEAAwE;IACxE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,uEAAuE;IACvE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC3B,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAE9B,uEAAuE;IACvE,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAEtB,uEAAuE;IACvE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IAEhC,IAAI,MAAc,CAAC;IAEnB,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxD,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;YACnC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,MAAM,UAAU,GACd,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YACtD,MAAM,SAAS,GAAG,UAAU,IAAI,IAAI,UAAU,EAAE,CAAC;YACjD,OAAO,CAAC,GAAG,CAAC,4CAA4C,SAAS,EAAE,CAAC,CAAC;YACrE,OAAO,CAAC,SAAS,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YAChC,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,GAAG;QACH,GAAG;QACH,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,SAAS,EAAE,CAAC;QACpB,CAAC;KACF,CAAC;AACJ,CAAC"}
|