@veedubin/boomerang-v3 0.1.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/.github/workflows/npm-publish.yml +58 -0
- package/.opencode/skills/boomerang-agent-builder/SKILL.md +226 -0
- package/.opencode/skills/boomerang-architect/SKILL.md +252 -0
- package/.opencode/skills/boomerang-coder/SKILL.md +283 -0
- package/.opencode/skills/boomerang-explorer/SKILL.md +58 -0
- package/.opencode/skills/boomerang-git/SKILL.md +115 -0
- package/.opencode/skills/boomerang-handoff/SKILL.md +209 -0
- package/.opencode/skills/boomerang-init/SKILL.md +117 -0
- package/.opencode/skills/boomerang-linter/SKILL.md +92 -0
- package/.opencode/skills/boomerang-orchestrator/SKILL.md +401 -0
- package/.opencode/skills/boomerang-release/SKILL.md +116 -0
- package/.opencode/skills/boomerang-scraper/SKILL.md +105 -0
- package/.opencode/skills/boomerang-tester/SKILL.md +107 -0
- package/.opencode/skills/boomerang-writer/SKILL.md +93 -0
- package/.opencode/skills/mcp-specialist/SKILL.md +130 -0
- package/.opencode/skills/researcher/SKILL.md +118 -0
- package/AGENTS.md +333 -0
- package/README.md +305 -0
- package/dist/index.js +13 -0
- package/dist/memini-client/index.js +560 -0
- package/dist/memini-client/schema.js +13 -0
- package/dist/memory/contradictions.js +119 -0
- package/dist/memory/graph.js +86 -0
- package/dist/memory/index.js +314 -0
- package/dist/memory/kg.js +111 -0
- package/dist/memory/schema.js +10 -0
- package/dist/memory/tiered.js +104 -0
- package/dist/memory/trust.js +148 -0
- package/dist/protocol/types.js +6 -0
- package/package.json +41 -0
- package/packages/opencode-plugin/src/asset-loader.ts +201 -0
- package/packages/opencode-plugin/src/git.ts +77 -0
- package/packages/opencode-plugin/src/index.ts +346 -0
- package/packages/opencode-plugin/src/memory.ts +109 -0
- package/packages/opencode-plugin/src/orchestrator.ts +263 -0
- package/packages/opencode-plugin/src/quality-gates.ts +75 -0
- package/packages/opencode-plugin/src/types.ts +141 -0
- package/src/index.ts +16 -0
- package/src/memini-client/index.ts +762 -0
- package/src/memini-client/schema.ts +60 -0
- package/src/memory/contradictions.ts +164 -0
- package/src/memory/graph.ts +116 -0
- package/src/memory/index.ts +422 -0
- package/src/memory/kg.ts +166 -0
- package/src/memory/schema.ts +274 -0
- package/src/memory/tiered.ts +133 -0
- package/src/memory/trust.ts +218 -0
- package/src/protocol/types.ts +79 -0
- package/tests/index.test.ts +58 -0
- package/tests/memini-client.test.ts +321 -0
- package/tests/memory/index.test.ts +214 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory graph integration for memini-ai
|
|
3
|
+
*
|
|
4
|
+
* Manages relationships between memory entries
|
|
5
|
+
*/
|
|
6
|
+
import { getClient } from '../memini-client/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Find memories related to a given memory
|
|
9
|
+
*/
|
|
10
|
+
export async function findRelatedMemories(memoryId, relationshipType, limit = 10, client) {
|
|
11
|
+
const mc = client ?? getClient();
|
|
12
|
+
// findRelatedMemories returns MemoryEntry[], but API shows MemoryEntry[] directly
|
|
13
|
+
const entries = await mc.findRelatedMemories(memoryId, relationshipType, limit);
|
|
14
|
+
return entries.map((e) => ({
|
|
15
|
+
id: e.id,
|
|
16
|
+
text: e.text,
|
|
17
|
+
vector: Array.from(e.vector || []),
|
|
18
|
+
sourceType: reverseMapSourceType(e.sourceType),
|
|
19
|
+
sourcePath: e.sourcePath || '',
|
|
20
|
+
timestamp: e.timestamp,
|
|
21
|
+
contentHash: e.contentHash || '',
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create a relationship between two memories
|
|
26
|
+
*/
|
|
27
|
+
export async function createRelationship(sourceId, targetId, type, confidence = 1.0, client) {
|
|
28
|
+
const mc = client ?? getClient();
|
|
29
|
+
await mc.createRelationship(sourceId, targetId, type, confidence);
|
|
30
|
+
return {
|
|
31
|
+
success: true,
|
|
32
|
+
sourceId,
|
|
33
|
+
targetId,
|
|
34
|
+
relationshipType: type,
|
|
35
|
+
confidence,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get summary of all relationships for a memory
|
|
40
|
+
*/
|
|
41
|
+
export async function getRelationshipSummary(memoryId, client) {
|
|
42
|
+
const mc = client ?? getClient();
|
|
43
|
+
const result = await mc.getRelationshipSummary(memoryId);
|
|
44
|
+
if (!result) {
|
|
45
|
+
return {
|
|
46
|
+
memoryId,
|
|
47
|
+
totalRelationships: 0,
|
|
48
|
+
byType: {
|
|
49
|
+
SUPERSEDES: 0,
|
|
50
|
+
RELATED_TO: 0,
|
|
51
|
+
CONTRADICTS: 0,
|
|
52
|
+
DERIVED_FROM: 0,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Ensure all relationship types are present
|
|
57
|
+
const allTypes = ['SUPERSEDES', 'RELATED_TO', 'CONTRADICTS', 'DERIVED_FROM'];
|
|
58
|
+
const completeByType = {};
|
|
59
|
+
for (const t of allTypes) {
|
|
60
|
+
completeByType[t] = result.byType?.[t] ?? 0;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
memoryId: result.memoryId,
|
|
64
|
+
totalRelationships: result.totalRelationships,
|
|
65
|
+
byType: completeByType,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Map memini-ai source type back to boomerang source type
|
|
70
|
+
*/
|
|
71
|
+
function reverseMapSourceType(type) {
|
|
72
|
+
switch (type) {
|
|
73
|
+
case 'session':
|
|
74
|
+
return 'conversation';
|
|
75
|
+
case 'file':
|
|
76
|
+
return 'file';
|
|
77
|
+
case 'web':
|
|
78
|
+
return 'web';
|
|
79
|
+
case 'boomerang':
|
|
80
|
+
return 'manual';
|
|
81
|
+
case 'project':
|
|
82
|
+
return 'manual';
|
|
83
|
+
default:
|
|
84
|
+
return type;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemorySystem - Singleton wrapper for memini-ai MCP client
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: Wraps memini-ai client while preserving the original API surface.
|
|
5
|
+
*/
|
|
6
|
+
import { initializeClient, } from '../memini-client/index.js';
|
|
7
|
+
import { DEFAULT_SEARCH_OPTIONS } from './schema.js';
|
|
8
|
+
const PROJECT_ID = process.env.BOOMERANG_PROJECT_ID || 'boomerang-default';
|
|
9
|
+
class MemorySystem {
|
|
10
|
+
static instance = null;
|
|
11
|
+
_initialized = false;
|
|
12
|
+
_client = null;
|
|
13
|
+
_status = null;
|
|
14
|
+
constructor() { }
|
|
15
|
+
/**
|
|
16
|
+
* Get the MemorySystem singleton instance
|
|
17
|
+
*/
|
|
18
|
+
static getInstance() {
|
|
19
|
+
if (!MemorySystem.instance) {
|
|
20
|
+
MemorySystem.instance = new MemorySystem();
|
|
21
|
+
}
|
|
22
|
+
return MemorySystem.instance;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if the system has been initialized
|
|
26
|
+
*/
|
|
27
|
+
isInitialized() {
|
|
28
|
+
return this._initialized;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the memory system (lazy initialization)
|
|
32
|
+
* Connects to memini-ai Python MCP server
|
|
33
|
+
*/
|
|
34
|
+
async initialize() {
|
|
35
|
+
if (this._initialized) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const pythonPath = process.env.PYTHON_PATH || 'python3';
|
|
39
|
+
const serverModule = process.env.MEMINI_SERVER_MODULE || 'memini_ai.server';
|
|
40
|
+
this._client = await initializeClient(pythonPath, serverModule);
|
|
41
|
+
// Get initial status
|
|
42
|
+
this._status = (await this._client.getStatus());
|
|
43
|
+
this._initialized = true;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get the underlying MCP client
|
|
47
|
+
*/
|
|
48
|
+
getClient() {
|
|
49
|
+
if (!this._client) {
|
|
50
|
+
throw new Error('MemorySystem not initialized. Call initialize() first.');
|
|
51
|
+
}
|
|
52
|
+
return this._client;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Add a new memory entry
|
|
56
|
+
*/
|
|
57
|
+
async addMemory(entry) {
|
|
58
|
+
this.ensureInitialized();
|
|
59
|
+
// Map source type to memini-ai format
|
|
60
|
+
const sourceType = mapSourceType(entry.sourceType);
|
|
61
|
+
const result = await this._client.addMemory({
|
|
62
|
+
text: entry.text,
|
|
63
|
+
sourceType,
|
|
64
|
+
sourcePath: entry.sourcePath,
|
|
65
|
+
metadataJson: entry.metadataJson,
|
|
66
|
+
});
|
|
67
|
+
// result is already a MemoryEntry from memini-client
|
|
68
|
+
const meminiEntry = result;
|
|
69
|
+
return adaptMemoryEntry(meminiEntry);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get a memory entry by ID
|
|
73
|
+
*/
|
|
74
|
+
async getMemory(id) {
|
|
75
|
+
this.ensureInitialized();
|
|
76
|
+
// Search for the memory by ID
|
|
77
|
+
const results = await this._client.search(`id:${id}`, { topK: 1 });
|
|
78
|
+
const match = results.find((m) => m.entry.id === id);
|
|
79
|
+
if (match) {
|
|
80
|
+
return adaptMemoryEntry(match.entry);
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Delete a memory entry by ID
|
|
86
|
+
* Note: memini-ai doesn't have direct delete, use adjustTrust with archive
|
|
87
|
+
*/
|
|
88
|
+
async deleteMemory(id) {
|
|
89
|
+
this.ensureInitialized();
|
|
90
|
+
try {
|
|
91
|
+
// Archive the memory instead of deleting
|
|
92
|
+
await this._client.adjustTrust(id, 'agent_ignored');
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* List unique source paths, optionally filtered by source type
|
|
101
|
+
*/
|
|
102
|
+
async listSources(sourceType) {
|
|
103
|
+
this.ensureInitialized();
|
|
104
|
+
// This is a simplified implementation
|
|
105
|
+
// In production, you'd want a dedicated list_sources tool
|
|
106
|
+
const results = await this._client.search('', { topK: 100 });
|
|
107
|
+
const pathSet = new Set();
|
|
108
|
+
for (const result of results) {
|
|
109
|
+
const entry = result.entry;
|
|
110
|
+
const entrySourceType = reverseMapSourceType(entry.sourceType);
|
|
111
|
+
if (entry.sourcePath && (!sourceType || entrySourceType === sourceType)) {
|
|
112
|
+
pathSet.add(entry.sourcePath);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return Array.from(pathSet);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Save a context entry for a session
|
|
119
|
+
*/
|
|
120
|
+
async saveContext(sessionId, context) {
|
|
121
|
+
this.ensureInitialized();
|
|
122
|
+
return this.addMemory({
|
|
123
|
+
text: context,
|
|
124
|
+
sourceType: 'conversation',
|
|
125
|
+
sourcePath: `session://${sessionId}`,
|
|
126
|
+
metadataJson: JSON.stringify({ type: 'context' }),
|
|
127
|
+
sessionId,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get the most recent context entry for a session
|
|
132
|
+
*/
|
|
133
|
+
async getContext(sessionId) {
|
|
134
|
+
this.ensureInitialized();
|
|
135
|
+
const results = await this._client.search(`session:${sessionId}`, { topK: 100 });
|
|
136
|
+
// Find the most recent context entry
|
|
137
|
+
let latestContext = null;
|
|
138
|
+
let latestTimestamp = 0;
|
|
139
|
+
for (const result of results) {
|
|
140
|
+
const entry = result.entry;
|
|
141
|
+
if (entry.metadataJson) {
|
|
142
|
+
try {
|
|
143
|
+
const meta = JSON.parse(entry.metadataJson);
|
|
144
|
+
if (meta.type === 'context') {
|
|
145
|
+
const ts = entry.timestamp;
|
|
146
|
+
if (ts > latestTimestamp) {
|
|
147
|
+
latestTimestamp = ts;
|
|
148
|
+
latestContext = adaptMemoryEntry(entry);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Not a context entry
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return latestContext;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Search memories using query string with optional search options
|
|
161
|
+
*/
|
|
162
|
+
async search(query, options) {
|
|
163
|
+
this.ensureInitialized();
|
|
164
|
+
const opts = {
|
|
165
|
+
...DEFAULT_SEARCH_OPTIONS,
|
|
166
|
+
...options,
|
|
167
|
+
};
|
|
168
|
+
const results = await this._client.search(query, { topK: opts.topK });
|
|
169
|
+
return results.map((r) => ({
|
|
170
|
+
entry: adaptMemoryEntry(r.entry),
|
|
171
|
+
score: r.score,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Ensure the system is initialized before performing operations
|
|
176
|
+
*/
|
|
177
|
+
ensureInitialized() {
|
|
178
|
+
if (!this.isInitialized()) {
|
|
179
|
+
throw new Error('MemorySystem not initialized. Call initialize() first.');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get server status
|
|
184
|
+
*/
|
|
185
|
+
async getStatus() {
|
|
186
|
+
if (!this._client) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
this._status = (await this._client.getStatus());
|
|
191
|
+
return this._status;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return this._status;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Check if the memory system is ready
|
|
199
|
+
*/
|
|
200
|
+
async isReady() {
|
|
201
|
+
const status = await this.getStatus();
|
|
202
|
+
return status?.memoryReady ?? false;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check if content already exists (deduplication)
|
|
206
|
+
*/
|
|
207
|
+
async contentExists(text) {
|
|
208
|
+
this.ensureInitialized();
|
|
209
|
+
const results = await this._client.search(text, { topK: 1 });
|
|
210
|
+
return results.length > 0;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Search memories using a pre-computed vector
|
|
214
|
+
* Note: memini-ai handles embedding internally
|
|
215
|
+
*/
|
|
216
|
+
async searchWithVector(vector, options) {
|
|
217
|
+
this.ensureInitialized();
|
|
218
|
+
// memini-ai doesn't expose vector search directly
|
|
219
|
+
// Fall back to text search
|
|
220
|
+
const opts = {
|
|
221
|
+
...DEFAULT_SEARCH_OPTIONS,
|
|
222
|
+
...options,
|
|
223
|
+
};
|
|
224
|
+
// Use vector as seed for approximate search
|
|
225
|
+
const results = await this._client.search(`vector:${vector.slice(0, 10).join(',')}`, { topK: opts.topK });
|
|
226
|
+
return results.map((r) => ({
|
|
227
|
+
entry: adaptMemoryEntry(r.entry),
|
|
228
|
+
score: r.score,
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get memories similar to a given memory
|
|
233
|
+
*/
|
|
234
|
+
async getSimilar(memoryId, options) {
|
|
235
|
+
this.ensureInitialized();
|
|
236
|
+
const memory = await this.getMemory(memoryId);
|
|
237
|
+
if (!memory) {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
return this.search(memory.text, options);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get memory statistics
|
|
244
|
+
*/
|
|
245
|
+
async getStats() {
|
|
246
|
+
this.ensureInitialized();
|
|
247
|
+
const status = await this.getStatus();
|
|
248
|
+
return {
|
|
249
|
+
count: status ? Object.keys(status).length : 0, // Placeholder
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Map boomerang source type to memini-ai source type
|
|
255
|
+
*/
|
|
256
|
+
function mapSourceType(type) {
|
|
257
|
+
switch (type) {
|
|
258
|
+
case 'conversation':
|
|
259
|
+
return 'session';
|
|
260
|
+
case 'file':
|
|
261
|
+
return 'file';
|
|
262
|
+
case 'web':
|
|
263
|
+
return 'web';
|
|
264
|
+
case 'manual':
|
|
265
|
+
return 'boomerang';
|
|
266
|
+
default:
|
|
267
|
+
return 'boomerang';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Map memini-ai source type back to boomerang source type
|
|
272
|
+
*/
|
|
273
|
+
function reverseMapSourceType(type) {
|
|
274
|
+
switch (type) {
|
|
275
|
+
case 'session':
|
|
276
|
+
return 'conversation';
|
|
277
|
+
case 'file':
|
|
278
|
+
return 'file';
|
|
279
|
+
case 'web':
|
|
280
|
+
return 'web';
|
|
281
|
+
case 'boomerang':
|
|
282
|
+
return 'manual';
|
|
283
|
+
case 'project':
|
|
284
|
+
return 'manual';
|
|
285
|
+
default:
|
|
286
|
+
return type;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Adapt a raw memory from memini-ai to our MemoryEntry type
|
|
291
|
+
*/
|
|
292
|
+
function adaptMemoryEntry(meminiEntry) {
|
|
293
|
+
return {
|
|
294
|
+
id: meminiEntry.id,
|
|
295
|
+
text: meminiEntry.text,
|
|
296
|
+
vector: Array.from(meminiEntry.vector || []),
|
|
297
|
+
sourceType: reverseMapSourceType(meminiEntry.sourceType),
|
|
298
|
+
sourcePath: meminiEntry.sourcePath || '',
|
|
299
|
+
timestamp: meminiEntry.timestamp,
|
|
300
|
+
contentHash: meminiEntry.contentHash || '',
|
|
301
|
+
metadataJson: meminiEntry.metadataJson,
|
|
302
|
+
sessionId: meminiEntry.sessionId,
|
|
303
|
+
projectId: meminiEntry.projectId,
|
|
304
|
+
score: meminiEntry.score,
|
|
305
|
+
trustScore: meminiEntry.trustScore,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Factory function to get the MemorySystem singleton
|
|
310
|
+
*/
|
|
311
|
+
export function getMemorySystem() {
|
|
312
|
+
return MemorySystem.getInstance();
|
|
313
|
+
}
|
|
314
|
+
export { MemorySystem };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge graph integration for memini-ai (Phase 3)
|
|
3
|
+
*
|
|
4
|
+
* Entity extraction, graph queries, and inference chains
|
|
5
|
+
*/
|
|
6
|
+
import { getClient } from '../memini-client/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Execute a formal knowledge graph query
|
|
9
|
+
*/
|
|
10
|
+
export async function queryKG(query, client) {
|
|
11
|
+
const mc = client ?? getClient();
|
|
12
|
+
const result = await mc.queryKG({
|
|
13
|
+
entity_a: query.entityA,
|
|
14
|
+
entity_b: query.entityB,
|
|
15
|
+
relationship_types: query.relationshipTypes,
|
|
16
|
+
inference_depth: query.inferenceDepth ?? 1,
|
|
17
|
+
limit: query.limit ?? 100,
|
|
18
|
+
});
|
|
19
|
+
if (!result) {
|
|
20
|
+
return {
|
|
21
|
+
success: false,
|
|
22
|
+
count: 0,
|
|
23
|
+
results: [],
|
|
24
|
+
error: 'Query failed',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
success: true,
|
|
29
|
+
count: result.entities.length,
|
|
30
|
+
results: result.entities,
|
|
31
|
+
error: undefined,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Extract entities from a specific memory
|
|
36
|
+
*/
|
|
37
|
+
export async function extractEntities(memoryId, client) {
|
|
38
|
+
const mc = client ?? getClient();
|
|
39
|
+
const result = await mc.extractEntities(memoryId);
|
|
40
|
+
return {
|
|
41
|
+
success: true,
|
|
42
|
+
memoryId,
|
|
43
|
+
entities: result.map((e) => ({
|
|
44
|
+
id: e.id,
|
|
45
|
+
name: e.name,
|
|
46
|
+
entityType: e.type ?? 'unknown',
|
|
47
|
+
metadata: e.properties ?? {},
|
|
48
|
+
})),
|
|
49
|
+
count: result.length,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get all connections to/from an entity
|
|
54
|
+
*/
|
|
55
|
+
export async function getEntityGraph(entityId, depth = 1, client) {
|
|
56
|
+
const mc = client ?? getClient();
|
|
57
|
+
const result = await mc.getEntityGraph(entityId, depth);
|
|
58
|
+
if (!result) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
entityId: result.entity.id,
|
|
63
|
+
incoming: result.connections.map((c) => c.target),
|
|
64
|
+
outgoing: result.connections.map((c) => c.target),
|
|
65
|
+
inferred: [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Find inference paths between two entities
|
|
70
|
+
*/
|
|
71
|
+
export async function getInferenceChain(startEntity, endEntity, maxDepth = 3, client) {
|
|
72
|
+
const mc = client ?? getClient();
|
|
73
|
+
const result = await mc.getInferenceChain(startEntity, endEntity, maxDepth);
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
startEntity,
|
|
77
|
+
endEntity,
|
|
78
|
+
paths: result.map((p) => ({
|
|
79
|
+
path: p.path.map((step) => step.entity),
|
|
80
|
+
confidence: p.confidence,
|
|
81
|
+
depth: p.depth,
|
|
82
|
+
})),
|
|
83
|
+
count: result.length,
|
|
84
|
+
error: undefined,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Search for entities by name
|
|
89
|
+
*/
|
|
90
|
+
export async function searchEntities(name, limit = 10, client) {
|
|
91
|
+
const mc = client ?? getClient();
|
|
92
|
+
const result = await mc.searchEntities(name, limit);
|
|
93
|
+
return {
|
|
94
|
+
success: true,
|
|
95
|
+
query: name,
|
|
96
|
+
entities: result.map((e) => ({
|
|
97
|
+
id: e.id,
|
|
98
|
+
name: e.name,
|
|
99
|
+
entityType: e.type ?? 'unknown',
|
|
100
|
+
metadata: e.properties ?? {},
|
|
101
|
+
})),
|
|
102
|
+
count: result.length,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get HTML visualization of the knowledge graph
|
|
107
|
+
*/
|
|
108
|
+
export async function getGraphVisualization(limit = 100, client) {
|
|
109
|
+
const mc = client ?? getClient();
|
|
110
|
+
return mc.getGraphVisualization(limit);
|
|
111
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiered loading integration for memini-ai
|
|
3
|
+
*
|
|
4
|
+
* L0: ~100 tokens - project summary for session start
|
|
5
|
+
* L1: ~2K tokens - key decisions for planning tasks
|
|
6
|
+
*/
|
|
7
|
+
import { getClient } from '../memini-client/index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Get L0 project summary (~100 tokens)
|
|
10
|
+
*
|
|
11
|
+
* Uses high-trust memories (trust >= 0.5) to generate a concise
|
|
12
|
+
* project summary suitable for session start auto-injection.
|
|
13
|
+
*/
|
|
14
|
+
export async function getTier0Summary(forceRefresh = false, client) {
|
|
15
|
+
const mc = client ?? getClient();
|
|
16
|
+
const result = await mc.getTier0Summary(forceRefresh);
|
|
17
|
+
if (!result) {
|
|
18
|
+
return {
|
|
19
|
+
tier: 'L0',
|
|
20
|
+
content: null,
|
|
21
|
+
tokenCount: 0,
|
|
22
|
+
cacheHit: false,
|
|
23
|
+
sourceCount: 0,
|
|
24
|
+
generatedAt: null,
|
|
25
|
+
error: 'Failed to generate summary',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
tier: 'L0',
|
|
30
|
+
content: result.content,
|
|
31
|
+
tokenCount: result.memoryCount,
|
|
32
|
+
cacheHit: false,
|
|
33
|
+
sourceCount: result.memoryCount,
|
|
34
|
+
generatedAt: result.generatedAt,
|
|
35
|
+
error: undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get L1 key decisions summary (~2K tokens)
|
|
40
|
+
*
|
|
41
|
+
* Uses promoted memories (trust >= 0.8) to generate a structured
|
|
42
|
+
* summary of key decisions and patterns for planning tasks.
|
|
43
|
+
*/
|
|
44
|
+
export async function getTier1Summary(forceRefresh = false, client) {
|
|
45
|
+
const mc = client ?? getClient();
|
|
46
|
+
const result = await mc.getTier1Summary(forceRefresh);
|
|
47
|
+
if (!result) {
|
|
48
|
+
return {
|
|
49
|
+
tier: 'L1',
|
|
50
|
+
content: null,
|
|
51
|
+
tokenCount: 0,
|
|
52
|
+
cacheHit: false,
|
|
53
|
+
sourceCount: 0,
|
|
54
|
+
generatedAt: null,
|
|
55
|
+
error: 'Failed to generate summary',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
tier: 'L1',
|
|
60
|
+
content: result.content,
|
|
61
|
+
tokenCount: result.memoryCount,
|
|
62
|
+
cacheHit: false,
|
|
63
|
+
sourceCount: result.memoryCount,
|
|
64
|
+
generatedAt: result.generatedAt,
|
|
65
|
+
error: undefined,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Trigger memory extraction manually
|
|
70
|
+
*
|
|
71
|
+
* Extracts facts/decisions/preferences from conversation text
|
|
72
|
+
* using LLM-based automatic extraction.
|
|
73
|
+
*/
|
|
74
|
+
export async function triggerExtraction(conversation, client) {
|
|
75
|
+
const mc = client ?? getClient();
|
|
76
|
+
const result = await mc.triggerExtraction(conversation);
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
count: result.length,
|
|
80
|
+
memoryIds: result,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Pre-compression extraction
|
|
85
|
+
*
|
|
86
|
+
* Captures current context and extracts memories before context
|
|
87
|
+
* compression/compaction. Helps preserve important details.
|
|
88
|
+
*/
|
|
89
|
+
export async function preconpressExtraction(context, client) {
|
|
90
|
+
const mc = client ?? getClient();
|
|
91
|
+
const result = await mc.preconpressExtraction(context);
|
|
92
|
+
if (!result) {
|
|
93
|
+
return {
|
|
94
|
+
extractionCount: 0,
|
|
95
|
+
memoriesCreated: [],
|
|
96
|
+
contextCaptured: context ?? '',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
extractionCount: result.extractedMemories.length,
|
|
101
|
+
memoriesCreated: result.extractedMemories,
|
|
102
|
+
contextCaptured: context ?? '',
|
|
103
|
+
};
|
|
104
|
+
}
|