@soulcraft/brainy 3.9.0 → 3.10.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 +89 -33
- package/dist/augmentations/KnowledgeAugmentation.d.ts +40 -0
- package/dist/augmentations/KnowledgeAugmentation.js +251 -0
- package/dist/augmentations/defaultAugmentations.d.ts +1 -0
- package/dist/augmentations/defaultAugmentations.js +5 -0
- package/dist/brainy.d.ts +11 -0
- package/dist/brainy.js +87 -1
- package/dist/embeddings/EmbeddingManager.js +14 -2
- package/dist/utils/mutex.d.ts +2 -0
- package/dist/utils/mutex.js +14 -3
- package/dist/vfs/ConceptSystem.d.ts +202 -0
- package/dist/vfs/ConceptSystem.js +598 -0
- package/dist/vfs/EntityManager.d.ts +75 -0
- package/dist/vfs/EntityManager.js +216 -0
- package/dist/vfs/EventRecorder.d.ts +83 -0
- package/dist/vfs/EventRecorder.js +292 -0
- package/dist/vfs/FSCompat.d.ts +85 -0
- package/dist/vfs/FSCompat.js +257 -0
- package/dist/vfs/GitBridge.d.ts +167 -0
- package/dist/vfs/GitBridge.js +537 -0
- package/dist/vfs/KnowledgeAugmentation.d.ts +104 -0
- package/dist/vfs/KnowledgeAugmentation.js +146 -0
- package/dist/vfs/KnowledgeLayer.d.ts +35 -0
- package/dist/vfs/KnowledgeLayer.js +443 -0
- package/dist/vfs/PathResolver.d.ts +96 -0
- package/dist/vfs/PathResolver.js +362 -0
- package/dist/vfs/PersistentEntitySystem.d.ts +163 -0
- package/dist/vfs/PersistentEntitySystem.js +525 -0
- package/dist/vfs/SemanticVersioning.d.ts +105 -0
- package/dist/vfs/SemanticVersioning.js +318 -0
- package/dist/vfs/VirtualFileSystem.d.ts +246 -0
- package/dist/vfs/VirtualFileSystem.js +1927 -0
- package/dist/vfs/importers/DirectoryImporter.d.ts +86 -0
- package/dist/vfs/importers/DirectoryImporter.js +298 -0
- package/dist/vfs/index.d.ts +19 -0
- package/dist/vfs/index.js +26 -0
- package/dist/vfs/streams/VFSReadStream.d.ts +19 -0
- package/dist/vfs/streams/VFSReadStream.js +54 -0
- package/dist/vfs/streams/VFSWriteStream.d.ts +21 -0
- package/dist/vfs/streams/VFSWriteStream.js +70 -0
- package/dist/vfs/types.d.ts +330 -0
- package/dist/vfs/types.js +46 -0
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Resolution System with High-Performance Caching
|
|
3
|
+
*
|
|
4
|
+
* PRODUCTION-READY path resolution for VFS
|
|
5
|
+
* Handles millions of paths efficiently with multi-layer caching
|
|
6
|
+
*/
|
|
7
|
+
import { Brainy } from '../brainy.js';
|
|
8
|
+
import { VFSEntity } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* High-performance path resolver with intelligent caching
|
|
11
|
+
*/
|
|
12
|
+
export declare class PathResolver {
|
|
13
|
+
private brain;
|
|
14
|
+
private rootEntityId;
|
|
15
|
+
private pathCache;
|
|
16
|
+
private parentCache;
|
|
17
|
+
private hotPaths;
|
|
18
|
+
private readonly maxCacheSize;
|
|
19
|
+
private readonly cacheTTL;
|
|
20
|
+
private readonly hotPathThreshold;
|
|
21
|
+
private cacheHits;
|
|
22
|
+
private cacheMisses;
|
|
23
|
+
private maintenanceTimer;
|
|
24
|
+
constructor(brain: Brainy, rootEntityId: string, config?: {
|
|
25
|
+
maxCacheSize?: number;
|
|
26
|
+
cacheTTL?: number;
|
|
27
|
+
hotPathThreshold?: number;
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a path to an entity ID
|
|
31
|
+
* Uses multi-layer caching for optimal performance
|
|
32
|
+
*/
|
|
33
|
+
resolve(path: string, options?: {
|
|
34
|
+
followSymlinks?: boolean;
|
|
35
|
+
cache?: boolean;
|
|
36
|
+
}): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Full path resolution by traversing the graph
|
|
39
|
+
*/
|
|
40
|
+
private fullResolve;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a child entity by name within a parent directory
|
|
43
|
+
*/
|
|
44
|
+
private resolveChild;
|
|
45
|
+
/**
|
|
46
|
+
* Get all children of a directory
|
|
47
|
+
*/
|
|
48
|
+
getChildren(dirId: string): Promise<VFSEntity[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Create a new path entry (for mkdir/writeFile)
|
|
51
|
+
*/
|
|
52
|
+
createPath(path: string, entityId: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Invalidate cache entries for a path and its children
|
|
55
|
+
*/
|
|
56
|
+
invalidatePath(path: string, recursive?: boolean): void;
|
|
57
|
+
/**
|
|
58
|
+
* Cache a path entry
|
|
59
|
+
*/
|
|
60
|
+
private cachePathEntry;
|
|
61
|
+
/**
|
|
62
|
+
* Check if a cache entry is still valid
|
|
63
|
+
*/
|
|
64
|
+
private isCacheValid;
|
|
65
|
+
/**
|
|
66
|
+
* Evict old cache entries (LRU with TTL)
|
|
67
|
+
*/
|
|
68
|
+
private evictOldEntries;
|
|
69
|
+
/**
|
|
70
|
+
* Start periodic cache maintenance
|
|
71
|
+
*/
|
|
72
|
+
private startCacheMaintenance;
|
|
73
|
+
/**
|
|
74
|
+
* Get entity by ID
|
|
75
|
+
*/
|
|
76
|
+
private getEntity;
|
|
77
|
+
private normalizePath;
|
|
78
|
+
private splitPath;
|
|
79
|
+
private joinPath;
|
|
80
|
+
private getParentPath;
|
|
81
|
+
private getBasename;
|
|
82
|
+
/**
|
|
83
|
+
* Cleanup resources
|
|
84
|
+
*/
|
|
85
|
+
cleanup(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Get cache statistics
|
|
88
|
+
*/
|
|
89
|
+
getStats(): {
|
|
90
|
+
cacheSize: number;
|
|
91
|
+
hotPaths: number;
|
|
92
|
+
hitRate: number;
|
|
93
|
+
hits: number;
|
|
94
|
+
misses: number;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Resolution System with High-Performance Caching
|
|
3
|
+
*
|
|
4
|
+
* PRODUCTION-READY path resolution for VFS
|
|
5
|
+
* Handles millions of paths efficiently with multi-layer caching
|
|
6
|
+
*/
|
|
7
|
+
import { VerbType } from '../types/graphTypes.js';
|
|
8
|
+
import { VFSError, VFSErrorCode } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* High-performance path resolver with intelligent caching
|
|
11
|
+
*/
|
|
12
|
+
export class PathResolver {
|
|
13
|
+
constructor(brain, rootEntityId, config) {
|
|
14
|
+
// Statistics
|
|
15
|
+
this.cacheHits = 0;
|
|
16
|
+
this.cacheMisses = 0;
|
|
17
|
+
// Maintenance timer
|
|
18
|
+
this.maintenanceTimer = null;
|
|
19
|
+
this.brain = brain;
|
|
20
|
+
this.rootEntityId = rootEntityId;
|
|
21
|
+
// Initialize caches
|
|
22
|
+
this.pathCache = new Map();
|
|
23
|
+
this.parentCache = new Map();
|
|
24
|
+
this.hotPaths = new Set();
|
|
25
|
+
// Configure cache
|
|
26
|
+
this.maxCacheSize = config?.maxCacheSize || 100000;
|
|
27
|
+
this.cacheTTL = config?.cacheTTL || 5 * 60 * 1000; // 5 minutes
|
|
28
|
+
this.hotPathThreshold = config?.hotPathThreshold || 10;
|
|
29
|
+
// Start cache maintenance
|
|
30
|
+
this.startCacheMaintenance();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a path to an entity ID
|
|
34
|
+
* Uses multi-layer caching for optimal performance
|
|
35
|
+
*/
|
|
36
|
+
async resolve(path, options) {
|
|
37
|
+
// Normalize path
|
|
38
|
+
const normalizedPath = this.normalizePath(path);
|
|
39
|
+
// Handle root
|
|
40
|
+
if (normalizedPath === '/') {
|
|
41
|
+
return this.rootEntityId;
|
|
42
|
+
}
|
|
43
|
+
// Check L1 cache (hot paths)
|
|
44
|
+
if (options?.cache !== false && this.hotPaths.has(normalizedPath)) {
|
|
45
|
+
const cached = this.pathCache.get(normalizedPath);
|
|
46
|
+
if (cached && this.isCacheValid(cached)) {
|
|
47
|
+
this.cacheHits++;
|
|
48
|
+
cached.hits++;
|
|
49
|
+
return cached.entityId;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Check L2 cache (regular cache)
|
|
53
|
+
if (options?.cache !== false && this.pathCache.has(normalizedPath)) {
|
|
54
|
+
const cached = this.pathCache.get(normalizedPath);
|
|
55
|
+
if (this.isCacheValid(cached)) {
|
|
56
|
+
this.cacheHits++;
|
|
57
|
+
cached.hits++;
|
|
58
|
+
// Promote to hot path if accessed frequently
|
|
59
|
+
if (cached.hits >= this.hotPathThreshold) {
|
|
60
|
+
this.hotPaths.add(normalizedPath);
|
|
61
|
+
}
|
|
62
|
+
return cached.entityId;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Remove stale entry
|
|
66
|
+
this.pathCache.delete(normalizedPath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.cacheMisses++;
|
|
70
|
+
// Try to resolve using parent cache
|
|
71
|
+
const parentPath = this.getParentPath(normalizedPath);
|
|
72
|
+
const name = this.getBasename(normalizedPath);
|
|
73
|
+
if (parentPath && this.pathCache.has(parentPath)) {
|
|
74
|
+
const parentCached = this.pathCache.get(parentPath);
|
|
75
|
+
if (this.isCacheValid(parentCached)) {
|
|
76
|
+
// We have the parent, just need to find the child
|
|
77
|
+
const entityId = await this.resolveChild(parentCached.entityId, name);
|
|
78
|
+
if (entityId) {
|
|
79
|
+
this.cachePathEntry(normalizedPath, entityId);
|
|
80
|
+
return entityId;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Full resolution required
|
|
85
|
+
const entityId = await this.fullResolve(normalizedPath, options);
|
|
86
|
+
// Cache the result
|
|
87
|
+
this.cachePathEntry(normalizedPath, entityId);
|
|
88
|
+
return entityId;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Full path resolution by traversing the graph
|
|
92
|
+
*/
|
|
93
|
+
async fullResolve(path, options) {
|
|
94
|
+
const parts = this.splitPath(path);
|
|
95
|
+
let currentId = this.rootEntityId;
|
|
96
|
+
let currentPath = '/';
|
|
97
|
+
for (const part of parts) {
|
|
98
|
+
if (!part)
|
|
99
|
+
continue; // Skip empty parts
|
|
100
|
+
// Find child with matching name
|
|
101
|
+
const childId = await this.resolveChild(currentId, part);
|
|
102
|
+
if (!childId) {
|
|
103
|
+
throw new VFSError(VFSErrorCode.ENOENT, `No such file or directory: ${path}`, path, 'resolve');
|
|
104
|
+
}
|
|
105
|
+
currentPath = this.joinPath(currentPath, part);
|
|
106
|
+
currentId = childId;
|
|
107
|
+
// Cache intermediate paths
|
|
108
|
+
this.cachePathEntry(currentPath, currentId);
|
|
109
|
+
// Handle symlinks if needed
|
|
110
|
+
if (options?.followSymlinks) {
|
|
111
|
+
const entity = await this.getEntity(currentId);
|
|
112
|
+
if (entity.metadata.vfsType === 'symlink') {
|
|
113
|
+
// Resolve symlink target
|
|
114
|
+
const target = entity.metadata.attributes?.target;
|
|
115
|
+
if (target) {
|
|
116
|
+
currentId = await this.resolve(target, options);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return currentId;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resolve a child entity by name within a parent directory
|
|
125
|
+
*/
|
|
126
|
+
async resolveChild(parentId, name) {
|
|
127
|
+
// Check parent cache first
|
|
128
|
+
const cachedChildren = this.parentCache.get(parentId);
|
|
129
|
+
if (cachedChildren && cachedChildren.has(name)) {
|
|
130
|
+
// We know this child exists, find it by path since parent queries don't work
|
|
131
|
+
const parentEntity = await this.getEntity(parentId);
|
|
132
|
+
const parentPath = parentEntity.metadata.path;
|
|
133
|
+
const childPath = this.joinPath(parentPath, name);
|
|
134
|
+
const pathResults = await this.brain.find({
|
|
135
|
+
where: { path: childPath },
|
|
136
|
+
limit: 1
|
|
137
|
+
});
|
|
138
|
+
if (pathResults.length > 0) {
|
|
139
|
+
return pathResults[0].entity.id;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Since parent field queries don't work reliably, construct the expected path
|
|
143
|
+
// and query by path instead
|
|
144
|
+
const parentEntity = await this.getEntity(parentId);
|
|
145
|
+
const parentPath = parentEntity.metadata.path;
|
|
146
|
+
const childPath = this.joinPath(parentPath, name);
|
|
147
|
+
const results = await this.brain.find({
|
|
148
|
+
where: { path: childPath },
|
|
149
|
+
limit: 1
|
|
150
|
+
});
|
|
151
|
+
if (results.length > 0) {
|
|
152
|
+
const childId = results[0].entity.id;
|
|
153
|
+
// Update parent cache
|
|
154
|
+
if (!this.parentCache.has(parentId)) {
|
|
155
|
+
this.parentCache.set(parentId, new Set());
|
|
156
|
+
}
|
|
157
|
+
this.parentCache.get(parentId).add(name);
|
|
158
|
+
return childId;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get all children of a directory
|
|
164
|
+
*/
|
|
165
|
+
async getChildren(dirId) {
|
|
166
|
+
// Use Brainy's relationship query to find all children
|
|
167
|
+
const results = await this.brain.find({
|
|
168
|
+
connected: {
|
|
169
|
+
from: dirId,
|
|
170
|
+
via: VerbType.Contains
|
|
171
|
+
},
|
|
172
|
+
limit: 10000 // Large limit for directories
|
|
173
|
+
});
|
|
174
|
+
// Filter and process valid VFS entities only
|
|
175
|
+
const validChildren = [];
|
|
176
|
+
const childNames = new Set();
|
|
177
|
+
for (const result of results) {
|
|
178
|
+
const entity = result.entity;
|
|
179
|
+
// Only include entities with proper VFS metadata and non-empty names
|
|
180
|
+
if (entity.metadata?.vfsType &&
|
|
181
|
+
entity.metadata?.name &&
|
|
182
|
+
entity.metadata?.path &&
|
|
183
|
+
entity.id !== dirId) { // Don't include the directory itself
|
|
184
|
+
validChildren.push(entity);
|
|
185
|
+
childNames.add(entity.metadata.name);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
this.parentCache.set(dirId, childNames);
|
|
189
|
+
return validChildren;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Create a new path entry (for mkdir/writeFile)
|
|
193
|
+
*/
|
|
194
|
+
async createPath(path, entityId) {
|
|
195
|
+
const normalizedPath = this.normalizePath(path);
|
|
196
|
+
// Cache the new path
|
|
197
|
+
this.cachePathEntry(normalizedPath, entityId);
|
|
198
|
+
// Update parent cache
|
|
199
|
+
const parentPath = this.getParentPath(normalizedPath);
|
|
200
|
+
const name = this.getBasename(normalizedPath);
|
|
201
|
+
if (parentPath) {
|
|
202
|
+
const parentId = await this.resolve(parentPath);
|
|
203
|
+
if (!this.parentCache.has(parentId)) {
|
|
204
|
+
this.parentCache.set(parentId, new Set());
|
|
205
|
+
}
|
|
206
|
+
this.parentCache.get(parentId).add(name);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Invalidate cache entries for a path and its children
|
|
211
|
+
*/
|
|
212
|
+
invalidatePath(path, recursive = false) {
|
|
213
|
+
const normalizedPath = this.normalizePath(path);
|
|
214
|
+
// Remove from all caches
|
|
215
|
+
this.pathCache.delete(normalizedPath);
|
|
216
|
+
this.hotPaths.delete(normalizedPath);
|
|
217
|
+
if (recursive) {
|
|
218
|
+
// Remove all paths that start with this path
|
|
219
|
+
const prefix = normalizedPath.endsWith('/') ? normalizedPath : normalizedPath + '/';
|
|
220
|
+
for (const [cachedPath] of this.pathCache) {
|
|
221
|
+
if (cachedPath.startsWith(prefix)) {
|
|
222
|
+
this.pathCache.delete(cachedPath);
|
|
223
|
+
this.hotPaths.delete(cachedPath);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Clear parent cache for the entity
|
|
228
|
+
const cached = this.pathCache.get(normalizedPath);
|
|
229
|
+
if (cached) {
|
|
230
|
+
this.parentCache.delete(cached.entityId);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Cache a path entry
|
|
235
|
+
*/
|
|
236
|
+
cachePathEntry(path, entityId) {
|
|
237
|
+
// Evict old entries if cache is full
|
|
238
|
+
if (this.pathCache.size >= this.maxCacheSize) {
|
|
239
|
+
this.evictOldEntries();
|
|
240
|
+
}
|
|
241
|
+
const existing = this.pathCache.get(path);
|
|
242
|
+
this.pathCache.set(path, {
|
|
243
|
+
entityId,
|
|
244
|
+
timestamp: Date.now(),
|
|
245
|
+
hits: existing?.hits || 0
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Check if a cache entry is still valid
|
|
250
|
+
*/
|
|
251
|
+
isCacheValid(entry) {
|
|
252
|
+
return (Date.now() - entry.timestamp) < this.cacheTTL;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Evict old cache entries (LRU with TTL)
|
|
256
|
+
*/
|
|
257
|
+
evictOldEntries() {
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
const entries = Array.from(this.pathCache.entries());
|
|
260
|
+
// Sort by least recently used (combination of timestamp and hits)
|
|
261
|
+
entries.sort((a, b) => {
|
|
262
|
+
const scoreA = a[1].timestamp + (a[1].hits * 60000); // Boost for hits
|
|
263
|
+
const scoreB = b[1].timestamp + (b[1].hits * 60000);
|
|
264
|
+
return scoreA - scoreB;
|
|
265
|
+
});
|
|
266
|
+
// Remove 10% of cache
|
|
267
|
+
const toRemove = Math.floor(this.maxCacheSize * 0.1);
|
|
268
|
+
for (let i = 0; i < toRemove && i < entries.length; i++) {
|
|
269
|
+
const [path] = entries[i];
|
|
270
|
+
this.pathCache.delete(path);
|
|
271
|
+
this.hotPaths.delete(path);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Start periodic cache maintenance
|
|
276
|
+
*/
|
|
277
|
+
startCacheMaintenance() {
|
|
278
|
+
this.maintenanceTimer = setInterval(() => {
|
|
279
|
+
// Clean up expired entries
|
|
280
|
+
const now = Date.now();
|
|
281
|
+
for (const [path, entry] of this.pathCache) {
|
|
282
|
+
if (!this.isCacheValid(entry)) {
|
|
283
|
+
this.pathCache.delete(path);
|
|
284
|
+
this.hotPaths.delete(path);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Log cache statistics (in production, send to monitoring)
|
|
288
|
+
const hitRate = this.cacheHits / (this.cacheHits + this.cacheMisses);
|
|
289
|
+
if ((this.cacheHits + this.cacheMisses) % 1000 === 0) {
|
|
290
|
+
console.log(`[PathResolver] Cache stats: ${Math.round(hitRate * 100)}% hit rate, ${this.pathCache.size} entries, ${this.hotPaths.size} hot paths`);
|
|
291
|
+
}
|
|
292
|
+
}, 60000); // Every minute
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Get entity by ID
|
|
296
|
+
*/
|
|
297
|
+
async getEntity(entityId) {
|
|
298
|
+
const entity = await this.brain.get(entityId);
|
|
299
|
+
if (!entity) {
|
|
300
|
+
throw new VFSError(VFSErrorCode.ENOENT, `Entity not found: ${entityId}`, undefined, 'getEntity');
|
|
301
|
+
}
|
|
302
|
+
return entity;
|
|
303
|
+
}
|
|
304
|
+
// ============= Path Utilities =============
|
|
305
|
+
normalizePath(path) {
|
|
306
|
+
// Remove multiple slashes, trailing slashes (except for root)
|
|
307
|
+
let normalized = path.replace(/\/+/g, '/');
|
|
308
|
+
if (normalized.length > 1 && normalized.endsWith('/')) {
|
|
309
|
+
normalized = normalized.slice(0, -1);
|
|
310
|
+
}
|
|
311
|
+
return normalized || '/';
|
|
312
|
+
}
|
|
313
|
+
splitPath(path) {
|
|
314
|
+
return this.normalizePath(path).split('/').filter(Boolean);
|
|
315
|
+
}
|
|
316
|
+
joinPath(parent, child) {
|
|
317
|
+
if (parent === '/')
|
|
318
|
+
return `/${child}`;
|
|
319
|
+
return `${parent}/${child}`;
|
|
320
|
+
}
|
|
321
|
+
getParentPath(path) {
|
|
322
|
+
const normalized = this.normalizePath(path);
|
|
323
|
+
if (normalized === '/')
|
|
324
|
+
return null;
|
|
325
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
326
|
+
if (lastSlash === 0)
|
|
327
|
+
return '/';
|
|
328
|
+
return normalized.substring(0, lastSlash);
|
|
329
|
+
}
|
|
330
|
+
getBasename(path) {
|
|
331
|
+
const normalized = this.normalizePath(path);
|
|
332
|
+
if (normalized === '/')
|
|
333
|
+
return '';
|
|
334
|
+
const lastSlash = normalized.lastIndexOf('/');
|
|
335
|
+
return normalized.substring(lastSlash + 1);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Cleanup resources
|
|
339
|
+
*/
|
|
340
|
+
cleanup() {
|
|
341
|
+
if (this.maintenanceTimer) {
|
|
342
|
+
clearInterval(this.maintenanceTimer);
|
|
343
|
+
this.maintenanceTimer = null;
|
|
344
|
+
}
|
|
345
|
+
this.pathCache.clear();
|
|
346
|
+
this.parentCache.clear();
|
|
347
|
+
this.hotPaths.clear();
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Get cache statistics
|
|
351
|
+
*/
|
|
352
|
+
getStats() {
|
|
353
|
+
return {
|
|
354
|
+
cacheSize: this.pathCache.size,
|
|
355
|
+
hotPaths: this.hotPaths.size,
|
|
356
|
+
hitRate: this.cacheHits / (this.cacheHits + this.cacheMisses),
|
|
357
|
+
hits: this.cacheHits,
|
|
358
|
+
misses: this.cacheMisses
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=PathResolver.js.map
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent Entity System for VFS
|
|
3
|
+
*
|
|
4
|
+
* Manages entities that evolve across files and time
|
|
5
|
+
* Not just story characters - any evolving entity: APIs, customers, services, models
|
|
6
|
+
* PRODUCTION-READY: Real implementation using Brainy
|
|
7
|
+
*/
|
|
8
|
+
import { Brainy } from '../brainy.js';
|
|
9
|
+
/**
|
|
10
|
+
* Persistent entity that exists across files and evolves over time
|
|
11
|
+
*/
|
|
12
|
+
export interface PersistentEntity {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
aliases: string[];
|
|
18
|
+
appearances: EntityAppearance[];
|
|
19
|
+
attributes: Record<string, any>;
|
|
20
|
+
created: number;
|
|
21
|
+
lastUpdated: number;
|
|
22
|
+
version: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* An appearance of an entity in a specific file/location
|
|
26
|
+
*/
|
|
27
|
+
export interface EntityAppearance {
|
|
28
|
+
id: string;
|
|
29
|
+
entityId: string;
|
|
30
|
+
filePath: string;
|
|
31
|
+
context: string;
|
|
32
|
+
position?: {
|
|
33
|
+
line?: number;
|
|
34
|
+
column?: number;
|
|
35
|
+
offset?: number;
|
|
36
|
+
};
|
|
37
|
+
timestamp: number;
|
|
38
|
+
version: number;
|
|
39
|
+
changes?: EntityChange[];
|
|
40
|
+
confidence: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A change/evolution to an entity
|
|
44
|
+
*/
|
|
45
|
+
export interface EntityChange {
|
|
46
|
+
field: string;
|
|
47
|
+
oldValue: any;
|
|
48
|
+
newValue: any;
|
|
49
|
+
timestamp: number;
|
|
50
|
+
source: string;
|
|
51
|
+
reason?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Configuration for persistent entities
|
|
55
|
+
*/
|
|
56
|
+
export interface PersistentEntityConfig {
|
|
57
|
+
autoExtract?: boolean;
|
|
58
|
+
similarityThreshold?: number;
|
|
59
|
+
maxAppearances?: number;
|
|
60
|
+
evolutionTracking?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Persistent Entity System
|
|
64
|
+
*
|
|
65
|
+
* Tracks entities that exist across multiple files and evolve over time
|
|
66
|
+
* Examples:
|
|
67
|
+
* - Story characters that appear in multiple chapters
|
|
68
|
+
* - API endpoints that evolve across documentation
|
|
69
|
+
* - Business entities that appear in multiple reports
|
|
70
|
+
* - Code classes/functions that span multiple files
|
|
71
|
+
*/
|
|
72
|
+
export declare class PersistentEntitySystem {
|
|
73
|
+
private brain;
|
|
74
|
+
private config;
|
|
75
|
+
private entityCache;
|
|
76
|
+
constructor(brain: Brainy, config?: PersistentEntityConfig);
|
|
77
|
+
/**
|
|
78
|
+
* Create a new persistent entity
|
|
79
|
+
*/
|
|
80
|
+
createEntity(entity: Omit<PersistentEntity, 'id' | 'created' | 'lastUpdated' | 'version' | 'appearances'>): Promise<string>;
|
|
81
|
+
/**
|
|
82
|
+
* Find an existing entity by name or attributes
|
|
83
|
+
*/
|
|
84
|
+
findEntity(query: {
|
|
85
|
+
name?: string;
|
|
86
|
+
type?: string;
|
|
87
|
+
attributes?: Record<string, any>;
|
|
88
|
+
similar?: string;
|
|
89
|
+
}): Promise<PersistentEntity[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Record an appearance of an entity in a file
|
|
92
|
+
*/
|
|
93
|
+
recordAppearance(entityId: string, filePath: string, context: string, options?: {
|
|
94
|
+
position?: EntityAppearance['position'];
|
|
95
|
+
confidence?: number;
|
|
96
|
+
extractChanges?: boolean;
|
|
97
|
+
}): Promise<string>;
|
|
98
|
+
/**
|
|
99
|
+
* Get entity evolution history
|
|
100
|
+
*/
|
|
101
|
+
getEvolution(entityId: string): Promise<{
|
|
102
|
+
entity: PersistentEntity;
|
|
103
|
+
timeline: Array<{
|
|
104
|
+
timestamp: number;
|
|
105
|
+
version: number;
|
|
106
|
+
changes: EntityChange[];
|
|
107
|
+
appearance?: EntityAppearance;
|
|
108
|
+
}>;
|
|
109
|
+
}>;
|
|
110
|
+
/**
|
|
111
|
+
* Find all appearances of an entity
|
|
112
|
+
*/
|
|
113
|
+
findAppearances(entityId: string, options?: {
|
|
114
|
+
filePath?: string;
|
|
115
|
+
since?: number;
|
|
116
|
+
until?: number;
|
|
117
|
+
minConfidence?: number;
|
|
118
|
+
}): Promise<EntityAppearance[]>;
|
|
119
|
+
/**
|
|
120
|
+
* Evolve an entity with new information
|
|
121
|
+
*/
|
|
122
|
+
evolveEntity(entityId: string, updates: Partial<Pick<PersistentEntity, 'name' | 'description' | 'aliases' | 'attributes'>>, source: string, reason?: string): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* Extract entities from content (auto-extraction)
|
|
125
|
+
*/
|
|
126
|
+
extractEntities(filePath: string, content: Buffer): Promise<string[]>;
|
|
127
|
+
/**
|
|
128
|
+
* Update references when a file moves
|
|
129
|
+
*/
|
|
130
|
+
updateReferences(oldPath: string, newPath: string): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Get entity by ID
|
|
133
|
+
*/
|
|
134
|
+
private getEntity;
|
|
135
|
+
/**
|
|
136
|
+
* Update stored entity
|
|
137
|
+
*/
|
|
138
|
+
private updateEntity;
|
|
139
|
+
/**
|
|
140
|
+
* Detect changes in entity from context
|
|
141
|
+
*/
|
|
142
|
+
private detectChanges;
|
|
143
|
+
/**
|
|
144
|
+
* Generate embedding for entity
|
|
145
|
+
*/
|
|
146
|
+
private generateEntityEmbedding;
|
|
147
|
+
/**
|
|
148
|
+
* Generate embedding for text
|
|
149
|
+
*/
|
|
150
|
+
private generateTextEmbedding;
|
|
151
|
+
/**
|
|
152
|
+
* Detect entity type from name and context
|
|
153
|
+
*/
|
|
154
|
+
private detectEntityType;
|
|
155
|
+
/**
|
|
156
|
+
* Extract context around a position
|
|
157
|
+
*/
|
|
158
|
+
private extractContext;
|
|
159
|
+
/**
|
|
160
|
+
* Clear entity cache
|
|
161
|
+
*/
|
|
162
|
+
clearCache(entityId?: string): void;
|
|
163
|
+
}
|