@levalicious/server-memory 0.0.10 → 0.0.12

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 CHANGED
@@ -108,11 +108,12 @@ Example:
108
108
 
109
109
  - **search_nodes**
110
110
  - Search for nodes using a regex pattern
111
- - Input: `query` (string), `entityCursor` (number, optional), `relationCursor` (number, optional)
112
- - Searches across:
113
- - Entity names
114
- - Entity types
115
- - Observation content
111
+ - Input:
112
+ - `query` (string): Regex pattern to search
113
+ - `sortBy` (string, optional): Sort field ("mtime", "obsMtime", or "name")
114
+ - `sortDir` (string, optional): Sort direction ("asc" or "desc")
115
+ - `entityCursor` (number, optional), `relationCursor` (number, optional)
116
+ - Searches across entity names, types, and observation content
116
117
  - Returns matching entities and their relations (paginated)
117
118
 
118
119
  - **open_nodes_filtered**
@@ -132,9 +133,15 @@ Example:
132
133
  - Silently skips non-existent nodes (paginated)
133
134
 
134
135
  - **get_neighbors**
135
- - Get neighboring entities connected to a specific entity within a given depth
136
- - Input: `entityName` (string), `depth` (number, default: 0), `withEntities` (boolean, default: false), `entityCursor` (number, optional), `relationCursor` (number, optional)
137
- - Returns relations (and optionally entities) connected within specified depth (paginated)
136
+ - Get names of neighboring entities connected to a specific entity within a given depth
137
+ - Input:
138
+ - `entityName` (string): The entity to find neighbors for
139
+ - `depth` (number, default: 1): Maximum traversal depth
140
+ - `sortBy` (string, optional): Sort field ("mtime", "obsMtime", or "name")
141
+ - `sortDir` (string, optional): Sort direction ("asc" or "desc")
142
+ - `cursor` (number, optional): Pagination cursor
143
+ - Returns neighbor names with timestamps (paginated)
144
+ - Use `open_nodes` to get full entity data for neighbors
138
145
 
139
146
  - **find_path**
140
147
  - Find a path between two entities in the knowledge graph
@@ -143,7 +150,11 @@ Example:
143
150
 
144
151
  - **get_entities_by_type**
145
152
  - Get all entities of a specific type
146
- - Input: `entityType` (string), `cursor` (number, optional)
153
+ - Input:
154
+ - `entityType` (string): Type to filter by
155
+ - `sortBy` (string, optional): Sort field ("mtime", "obsMtime", or "name")
156
+ - `sortDir` (string, optional): Sort direction ("asc" or "desc")
157
+ - `cursor` (number, optional)
147
158
  - Returns all entities matching the specified type (paginated)
148
159
 
149
160
  - **get_entity_types**
@@ -163,8 +174,11 @@ Example:
163
174
 
164
175
  - **get_orphaned_entities**
165
176
  - Get entities that have no relations (orphaned entities)
166
- - Input: `strict` (boolean, default: false), `cursor` (number, optional)
167
- - In strict mode, returns entities not connected to 'Self' entity (directly or indirectly)
177
+ - Input:
178
+ - `strict` (boolean, default: false): If true, returns entities not connected to 'Self' entity
179
+ - `sortBy` (string, optional): Sort field ("mtime", "obsMtime", or "name")
180
+ - `sortDir` (string, optional): Sort direction ("asc" or "desc")
181
+ - `cursor` (number, optional)
168
182
  - Returns entities with no connections (paginated)
169
183
 
170
184
  - **validate_graph**
@@ -172,22 +186,22 @@ Example:
172
186
  - No input required
173
187
  - Returns missing entities referenced in relations and observation limit violations
174
188
 
175
- - **evaluate_bcl**
176
- - Evaluate a Binary Combinatory Logic (BCL) program
177
- - Input: `program` (string), `maxSteps` (number, default: 1000000)
178
- - BCL syntax: T:=00|01|1TT where 00=K, 01=S, 1=application
179
- - Returns evaluation result with halt status
180
-
181
- - **add_bcl_term**
182
- - Add a BCL term to the constructor, maintaining valid syntax
183
- - Input: `term` (string)
184
- - Valid values: '1' or 'App' (application), '00' or 'K' (K combinator), '01' or 'S' (S combinator)
185
- - Returns completion status
186
-
187
- - **clear_bcl_term**
188
- - Clear the current BCL term being constructed and reset the constructor state
189
- - No input required
190
- - Resets BCL constructor
189
+ - **decode_timestamp**
190
+ - Decode a millisecond timestamp to human-readable UTC format
191
+ - Input:
192
+ - `timestamp` (number, optional): Millisecond timestamp to decode. If omitted, returns current time
193
+ - `relative` (boolean, optional): If true, include relative time (e.g., "3 days ago")
194
+ - Returns timestamp, ISO 8601 string, formatted UTC string, and optional relative time
195
+ - Useful for interpreting `mtime`/`obsMtime` values from entities
196
+
197
+ - **random_walk**
198
+ - Perform a random walk from a starting entity, following random relations
199
+ - Input:
200
+ - `start` (string): Name of the entity to start the walk from
201
+ - `depth` (number, default: 3): Number of hops to take
202
+ - `seed` (string, optional): Seed for reproducible walks
203
+ - Returns the terminal entity name and the path taken
204
+ - Useful for serendipitous exploration of the knowledge graph
191
205
 
192
206
  - **sequentialthinking**
193
207
  - Record a thought in the knowledge graph
package/dist/index.js CHANGED
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { createServer } from "./server.js";
4
+ // Workaround: Node 24 segfaults on exit when any N-API addon is loaded,
5
+ // even a bare no-op module. This is a confirmed Node bug, not ours.
6
+ // Force a clean exit to avoid the cosmetic segfault.
7
+ // process.on('exit', () => { process._exit(0); });
4
8
  const server = createServer();
5
9
  const transport = new StdioServerTransport();
6
10
  server.connect(transport).catch((error) => {
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * migrate-jsonl.ts — Convert a JSONL knowledge graph to binary (GraphFile + StringTable).
4
+ *
5
+ * Usage:
6
+ * npx tsx scripts/migrate-jsonl.ts [path/to/memory.json]
7
+ *
8
+ * If no path given, defaults to ~/.local/share/memory/vscode.json
9
+ *
10
+ * Creates:
11
+ * <base>.graph — binary graph store
12
+ * <base>.strings — binary string table
13
+ *
14
+ * The original .json file is NOT modified or deleted.
15
+ */
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+ import * as readline from 'readline';
19
+ import { StringTable } from '../src/stringtable.js';
20
+ import { GraphFile, DIR_FORWARD, DIR_BACKWARD } from '../src/graphfile.js';
21
+ async function migrate(jsonlPath) {
22
+ const dir = path.dirname(jsonlPath);
23
+ const base = path.basename(jsonlPath, path.extname(jsonlPath));
24
+ const graphPath = path.join(dir, `${base}.graph`);
25
+ const strPath = path.join(dir, `${base}.strings`);
26
+ // Safety: don't clobber existing binary files
27
+ if (fs.existsSync(graphPath) || fs.existsSync(strPath)) {
28
+ console.error(`ERROR: Binary files already exist:\n ${graphPath}\n ${strPath}`);
29
+ console.error('Delete them first if you want to re-migrate.');
30
+ process.exit(1);
31
+ }
32
+ console.log(`Source: ${jsonlPath}`);
33
+ console.log(`Target: ${graphPath}`);
34
+ console.log(` ${strPath}`);
35
+ console.log();
36
+ // --- Pass 1: Parse JSONL, collect entities and relations ---
37
+ const entities = [];
38
+ const relations = [];
39
+ let lineNum = 0;
40
+ let parseErrors = 0;
41
+ const fileStream = fs.createReadStream(jsonlPath, { encoding: 'utf-8' });
42
+ const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
43
+ for await (const line of rl) {
44
+ lineNum++;
45
+ const trimmed = line.trim();
46
+ if (!trimmed)
47
+ continue;
48
+ try {
49
+ const obj = JSON.parse(trimmed);
50
+ if (obj.type === 'entity') {
51
+ entities.push(obj);
52
+ }
53
+ else if (obj.type === 'relation') {
54
+ relations.push(obj);
55
+ }
56
+ }
57
+ catch (e) {
58
+ parseErrors++;
59
+ if (parseErrors <= 5) {
60
+ console.warn(` WARN: parse error on line ${lineNum}: ${e.message}`);
61
+ }
62
+ }
63
+ }
64
+ console.log(`Parsed: ${entities.length} entities, ${relations.length} relations`);
65
+ if (parseErrors > 0) {
66
+ console.warn(` (${parseErrors} lines had parse errors — skipped)`);
67
+ }
68
+ // --- Pass 2: Build binary store ---
69
+ // Start with a generous initial size to reduce remaps.
70
+ // Rough estimate: 64B per entity + ~200B string overhead per entity + 24B per adj entry
71
+ const estimatedSize = Math.max(65536, entities.length * 300 + relations.length * 100);
72
+ const st = new StringTable(strPath, estimatedSize);
73
+ const gf = new GraphFile(graphPath, st, estimatedSize);
74
+ // Create all entities first, build name→offset map
75
+ const nameToOffset = new Map();
76
+ let created = 0;
77
+ let skippedDuplicates = 0;
78
+ for (const e of entities) {
79
+ if (nameToOffset.has(e.name)) {
80
+ skippedDuplicates++;
81
+ continue;
82
+ }
83
+ const mtime = BigInt(e.mtime ?? 0);
84
+ const obsMtime = BigInt(e.obsMtime ?? 0);
85
+ const rec = gf.createEntity(e.name, e.entityType, mtime, obsMtime);
86
+ // Add observations (max 2)
87
+ const obs = e.observations.slice(0, 2);
88
+ for (const o of obs) {
89
+ // Truncate to 140 chars if needed
90
+ const truncated = o.length > 140 ? o.substring(0, 140) : o;
91
+ gf.addObservation(rec.offset, truncated, obsMtime);
92
+ }
93
+ // Fix timestamps (addObservation clobbers mtime)
94
+ if (obs.length > 0) {
95
+ const updated = gf.readEntity(rec.offset);
96
+ updated.mtime = mtime;
97
+ updated.obsMtime = obsMtime;
98
+ gf.updateEntity(updated);
99
+ }
100
+ nameToOffset.set(e.name, rec.offset);
101
+ created++;
102
+ if (created % 1000 === 0) {
103
+ process.stdout.write(` Entities: ${created}/${entities.length}\r`);
104
+ }
105
+ }
106
+ console.log(` Entities: ${created} created, ${skippedDuplicates} duplicates skipped`);
107
+ // Create all relations
108
+ let relCreated = 0;
109
+ let relSkipped = 0;
110
+ for (const r of relations) {
111
+ const fromOffset = nameToOffset.get(r.from);
112
+ const toOffset = nameToOffset.get(r.to);
113
+ if (fromOffset === undefined || toOffset === undefined) {
114
+ relSkipped++;
115
+ continue;
116
+ }
117
+ const mtime = BigInt(r.mtime ?? 0);
118
+ const relTypeId = Number(st.intern(r.relationType));
119
+ // Forward edge on 'from'
120
+ const forwardEntry = {
121
+ targetOffset: toOffset,
122
+ direction: DIR_FORWARD,
123
+ relTypeId,
124
+ mtime,
125
+ };
126
+ gf.addEdge(fromOffset, forwardEntry);
127
+ // Backward edge on 'to' (intern again to bump refcount)
128
+ const relTypeId2 = Number(st.intern(r.relationType));
129
+ const backwardEntry = {
130
+ targetOffset: fromOffset,
131
+ direction: DIR_BACKWARD,
132
+ relTypeId: relTypeId2,
133
+ mtime,
134
+ };
135
+ gf.addEdge(toOffset, backwardEntry);
136
+ relCreated++;
137
+ if (relCreated % 1000 === 0) {
138
+ process.stdout.write(` Relations: ${relCreated}/${relations.length}\r`);
139
+ }
140
+ }
141
+ console.log(` Relations: ${relCreated} created, ${relSkipped} skipped (missing endpoints)`);
142
+ // Sync and close
143
+ gf.sync();
144
+ st.sync();
145
+ // Report sizes
146
+ const graphSize = fs.statSync(graphPath).size;
147
+ const strSize = fs.statSync(strPath).size;
148
+ const jsonlSize = fs.statSync(jsonlPath).size;
149
+ console.log();
150
+ console.log(`File sizes:`);
151
+ console.log(` JSONL: ${(jsonlSize / 1024 / 1024).toFixed(2)} MB`);
152
+ console.log(` Graph: ${(graphSize / 1024 / 1024).toFixed(2)} MB`);
153
+ console.log(` Strings: ${(strSize / 1024 / 1024).toFixed(2)} MB`);
154
+ console.log(` Binary total: ${((graphSize + strSize) / 1024 / 1024).toFixed(2)} MB`);
155
+ gf.close();
156
+ st.close();
157
+ console.log();
158
+ console.log('Migration complete. Original JSONL file preserved.');
159
+ }
160
+ // --- Main ---
161
+ const inputPath = process.argv[2] || path.join(process.env.HOME || process.env.USERPROFILE || '.', '.local', 'share', 'memory', 'vscode.json');
162
+ if (!fs.existsSync(inputPath)) {
163
+ console.error(`ERROR: File not found: ${inputPath}`);
164
+ process.exit(1);
165
+ }
166
+ migrate(inputPath).catch(err => {
167
+ console.error('Migration failed:', err);
168
+ process.exit(1);
169
+ });
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Quick verification that binary files are readable after migration.
4
+ */
5
+ import { StringTable } from '../src/stringtable.js';
6
+ import { GraphFile, DIR_FORWARD } from '../src/graphfile.js';
7
+ import * as path from 'path';
8
+ const inputPath = process.argv[2] || path.join(process.env.HOME || '.', '.local', 'share', 'memory', 'vscode.json');
9
+ const dir = path.dirname(inputPath);
10
+ const base = path.basename(inputPath, path.extname(inputPath));
11
+ const graphPath = path.join(dir, `${base}.graph`);
12
+ const strPath = path.join(dir, `${base}.strings`);
13
+ const st = new StringTable(strPath);
14
+ const gf = new GraphFile(graphPath, st);
15
+ const offsets = gf.getAllEntityOffsets();
16
+ console.log(`Entity count: ${offsets.length}`);
17
+ // Sample first 5 entities
18
+ console.log('\nSample entities:');
19
+ for (const off of offsets.slice(0, 5)) {
20
+ const rec = gf.readEntity(off);
21
+ const name = st.get(BigInt(rec.nameId));
22
+ const type = st.get(BigInt(rec.typeId));
23
+ const obs = [];
24
+ if (rec.obs0Id)
25
+ obs.push(st.get(BigInt(rec.obs0Id)));
26
+ if (rec.obs1Id)
27
+ obs.push(st.get(BigInt(rec.obs1Id)));
28
+ console.log(` ${name} [${type}] obs=${obs.length} mtime=${Number(rec.mtime)}`);
29
+ }
30
+ // Count total relations
31
+ let relCount = 0;
32
+ for (const off of offsets) {
33
+ const edges = gf.getEdges(off);
34
+ relCount += edges.filter(e => e.direction === DIR_FORWARD).length;
35
+ }
36
+ console.log(`\nRelation count (forward edges): ${relCount}`);
37
+ gf.close();
38
+ st.close();
39
+ console.log('\nVerification passed.');