@soulcraft/brainy 3.10.1 → 3.13.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.
@@ -40,10 +40,12 @@ export declare class PathResolver {
40
40
  private fullResolve;
41
41
  /**
42
42
  * Resolve a child entity by name within a parent directory
43
+ * Uses proper graph relationships instead of metadata queries
43
44
  */
44
45
  private resolveChild;
45
46
  /**
46
47
  * Get all children of a directory
48
+ * Uses proper graph relationships to traverse the tree
47
49
  */
48
50
  getChildren(dirId: string): Promise<VFSEntity[]>;
49
51
  /**
@@ -122,69 +122,56 @@ export class PathResolver {
122
122
  }
123
123
  /**
124
124
  * Resolve a child entity by name within a parent directory
125
+ * Uses proper graph relationships instead of metadata queries
125
126
  */
126
127
  async resolveChild(parentId, name) {
127
128
  // Check parent cache first
128
129
  const cachedChildren = this.parentCache.get(parentId);
129
130
  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
- }
131
+ // Use cached knowledge to quickly find the child
132
+ // Still need to verify it exists
141
133
  }
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
134
+ // Use proper graph traversal to find children
135
+ // Get all relationships where parentId contains other entities
136
+ const relations = await this.brain.getRelations({
137
+ from: parentId,
138
+ type: VerbType.Contains
150
139
  });
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());
140
+ // Find the child with matching name
141
+ for (const relation of relations) {
142
+ const childEntity = await this.brain.get(relation.to);
143
+ if (childEntity && childEntity.metadata?.name === name) {
144
+ // Update parent cache
145
+ if (!this.parentCache.has(parentId)) {
146
+ this.parentCache.set(parentId, new Set());
147
+ }
148
+ this.parentCache.get(parentId).add(name);
149
+ return childEntity.id;
156
150
  }
157
- this.parentCache.get(parentId).add(name);
158
- return childId;
159
151
  }
160
152
  return null;
161
153
  }
162
154
  /**
163
155
  * Get all children of a directory
156
+ * Uses proper graph relationships to traverse the tree
164
157
  */
165
158
  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
159
+ // Use proper graph API to get all Contains relationships from this directory
160
+ const relations = await this.brain.getRelations({
161
+ from: dirId,
162
+ type: VerbType.Contains
173
163
  });
174
- // Filter and process valid VFS entities only
175
164
  const validChildren = [];
176
165
  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
166
+ // Fetch all child entities
167
+ for (const relation of relations) {
168
+ const entity = await this.brain.get(relation.to);
169
+ if (entity && entity.metadata?.vfsType && entity.metadata?.name) {
184
170
  validChildren.push(entity);
185
171
  childNames.add(entity.metadata.name);
186
172
  }
187
173
  }
174
+ // Update cache
188
175
  this.parentCache.set(dirId, childNames);
189
176
  return validChildren;
190
177
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * VFS Tree Utilities
3
+ * Provides safe tree operations that prevent common recursion issues
4
+ */
5
+ import { VFSEntity } from './types.js';
6
+ export interface TreeNode {
7
+ name: string;
8
+ path: string;
9
+ type: 'file' | 'directory';
10
+ entityId?: string;
11
+ children?: TreeNode[];
12
+ metadata?: any;
13
+ }
14
+ export interface TreeOptions {
15
+ maxDepth?: number;
16
+ includeHidden?: boolean;
17
+ filter?: (node: VFSEntity) => boolean;
18
+ sort?: 'name' | 'modified' | 'size';
19
+ expandAll?: boolean;
20
+ }
21
+ /**
22
+ * Tree utility functions for VFS
23
+ * These functions ensure proper tree structure without recursion issues
24
+ */
25
+ export declare class VFSTreeUtils {
26
+ /**
27
+ * Build a safe tree structure from VFS entities
28
+ * Guarantees no directory appears as its own child
29
+ */
30
+ static buildTree(entities: VFSEntity[], rootPath?: string, options?: TreeOptions): TreeNode;
31
+ /**
32
+ * Get direct children only - guaranteed no self-inclusion
33
+ */
34
+ static getDirectChildren(entities: VFSEntity[], parentPath: string): VFSEntity[];
35
+ /**
36
+ * Get all descendants (recursive children)
37
+ */
38
+ static getDescendants(entities: VFSEntity[], ancestorPath: string, includeAncestor?: boolean): VFSEntity[];
39
+ /**
40
+ * Flatten a tree structure back to a list
41
+ */
42
+ static flattenTree(node: TreeNode): TreeNode[];
43
+ /**
44
+ * Find a node in the tree by path
45
+ */
46
+ static findNode(root: TreeNode, targetPath: string): TreeNode | null;
47
+ /**
48
+ * Calculate tree statistics
49
+ */
50
+ static getTreeStats(node: TreeNode): {
51
+ totalNodes: number;
52
+ files: number;
53
+ directories: number;
54
+ maxDepth: number;
55
+ totalSize?: number;
56
+ };
57
+ private static getParentPath;
58
+ private static sortTreeNodes;
59
+ private static limitDepth;
60
+ /**
61
+ * Validate tree structure - ensures no recursion
62
+ */
63
+ static validateTree(node: TreeNode, visited?: Set<string>): {
64
+ valid: boolean;
65
+ errors: string[];
66
+ };
67
+ }
@@ -0,0 +1,268 @@
1
+ /**
2
+ * VFS Tree Utilities
3
+ * Provides safe tree operations that prevent common recursion issues
4
+ */
5
+ /**
6
+ * Tree utility functions for VFS
7
+ * These functions ensure proper tree structure without recursion issues
8
+ */
9
+ export class VFSTreeUtils {
10
+ /**
11
+ * Build a safe tree structure from VFS entities
12
+ * Guarantees no directory appears as its own child
13
+ */
14
+ static buildTree(entities, rootPath = '/', options = {}) {
15
+ const pathToEntity = new Map();
16
+ const pathToNode = new Map();
17
+ // First pass: index all entities by path
18
+ for (const entity of entities) {
19
+ const path = entity.metadata.path;
20
+ // Critical: Skip if entity IS the root we're building from
21
+ if (path === rootPath) {
22
+ continue;
23
+ }
24
+ pathToEntity.set(path, entity);
25
+ }
26
+ // Create root node
27
+ const rootNode = {
28
+ name: rootPath === '/' ? 'root' : rootPath.split('/').pop(),
29
+ path: rootPath,
30
+ type: 'directory',
31
+ children: []
32
+ };
33
+ pathToNode.set(rootPath, rootNode);
34
+ // Second pass: build tree structure
35
+ const sortedPaths = Array.from(pathToEntity.keys()).sort();
36
+ for (const path of sortedPaths) {
37
+ const entity = pathToEntity.get(path);
38
+ // Apply filter if provided
39
+ if (options.filter && !options.filter(entity)) {
40
+ continue;
41
+ }
42
+ // Skip hidden files if requested
43
+ if (!options.includeHidden && entity.metadata.name.startsWith('.')) {
44
+ continue;
45
+ }
46
+ // Create node for this entity
47
+ const node = {
48
+ name: entity.metadata.name,
49
+ path: entity.metadata.path,
50
+ type: entity.metadata.vfsType === 'directory' ? 'directory' : 'file',
51
+ entityId: entity.id,
52
+ metadata: entity.metadata
53
+ };
54
+ if (entity.metadata.vfsType === 'directory') {
55
+ node.children = [];
56
+ }
57
+ pathToNode.set(path, node);
58
+ // Find parent and attach
59
+ const parentPath = this.getParentPath(path);
60
+ const parentNode = pathToNode.get(parentPath);
61
+ if (parentNode && parentNode.children) {
62
+ parentNode.children.push(node);
63
+ }
64
+ }
65
+ // Sort children if requested
66
+ if (options.sort) {
67
+ this.sortTreeNodes(rootNode, options.sort);
68
+ }
69
+ // Apply depth limit if specified
70
+ if (options.maxDepth !== undefined) {
71
+ this.limitDepth(rootNode, options.maxDepth);
72
+ }
73
+ return rootNode;
74
+ }
75
+ /**
76
+ * Get direct children only - guaranteed no self-inclusion
77
+ */
78
+ static getDirectChildren(entities, parentPath) {
79
+ const children = [];
80
+ const parentDepth = parentPath === '/' ? 0 : parentPath.split('/').length - 1;
81
+ for (const entity of entities) {
82
+ const path = entity.metadata.path;
83
+ // Critical check 1: Skip if this IS the parent
84
+ if (path === parentPath) {
85
+ continue;
86
+ }
87
+ // Check if entity is a direct child
88
+ if (path.startsWith(parentPath)) {
89
+ const relativePath = parentPath === '/'
90
+ ? path.substring(1)
91
+ : path.substring(parentPath.length + 1);
92
+ // Direct child has no additional slashes
93
+ if (!relativePath.includes('/')) {
94
+ children.push(entity);
95
+ }
96
+ }
97
+ }
98
+ return children;
99
+ }
100
+ /**
101
+ * Get all descendants (recursive children)
102
+ */
103
+ static getDescendants(entities, ancestorPath, includeAncestor = false) {
104
+ const descendants = [];
105
+ for (const entity of entities) {
106
+ const path = entity.metadata.path;
107
+ // Include ancestor only if explicitly requested
108
+ if (path === ancestorPath) {
109
+ if (includeAncestor) {
110
+ descendants.push(entity);
111
+ }
112
+ continue;
113
+ }
114
+ // Check if entity is under ancestor path
115
+ const prefix = ancestorPath === '/' ? '/' : ancestorPath + '/';
116
+ if (path.startsWith(prefix)) {
117
+ descendants.push(entity);
118
+ }
119
+ }
120
+ return descendants;
121
+ }
122
+ /**
123
+ * Flatten a tree structure back to a list
124
+ */
125
+ static flattenTree(node) {
126
+ const result = [node];
127
+ if (node.children) {
128
+ for (const child of node.children) {
129
+ result.push(...this.flattenTree(child));
130
+ }
131
+ }
132
+ return result;
133
+ }
134
+ /**
135
+ * Find a node in the tree by path
136
+ */
137
+ static findNode(root, targetPath) {
138
+ if (root.path === targetPath) {
139
+ return root;
140
+ }
141
+ if (root.children) {
142
+ for (const child of root.children) {
143
+ const found = this.findNode(child, targetPath);
144
+ if (found)
145
+ return found;
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+ /**
151
+ * Calculate tree statistics
152
+ */
153
+ static getTreeStats(node) {
154
+ let stats = {
155
+ totalNodes: 0,
156
+ files: 0,
157
+ directories: 0,
158
+ maxDepth: 0,
159
+ totalSize: 0
160
+ };
161
+ function traverse(n, depth) {
162
+ stats.totalNodes++;
163
+ stats.maxDepth = Math.max(stats.maxDepth, depth);
164
+ if (n.type === 'file') {
165
+ stats.files++;
166
+ if (n.metadata?.size) {
167
+ stats.totalSize += n.metadata.size;
168
+ }
169
+ }
170
+ else {
171
+ stats.directories++;
172
+ }
173
+ if (n.children) {
174
+ for (const child of n.children) {
175
+ traverse(child, depth + 1);
176
+ }
177
+ }
178
+ }
179
+ traverse(node, 0);
180
+ return stats;
181
+ }
182
+ // Helper methods
183
+ static getParentPath(path) {
184
+ if (path === '/')
185
+ return '/';
186
+ const parts = path.split('/');
187
+ parts.pop();
188
+ return parts.length === 1 ? '/' : parts.join('/');
189
+ }
190
+ static sortTreeNodes(node, sortBy) {
191
+ if (!node.children)
192
+ return;
193
+ node.children.sort((a, b) => {
194
+ // Directories first, then files
195
+ if (a.type !== b.type) {
196
+ return a.type === 'directory' ? -1 : 1;
197
+ }
198
+ switch (sortBy) {
199
+ case 'name':
200
+ return a.name.localeCompare(b.name);
201
+ case 'modified':
202
+ const aTime = a.metadata?.modified || 0;
203
+ const bTime = b.metadata?.modified || 0;
204
+ return bTime - aTime;
205
+ case 'size':
206
+ const aSize = a.metadata?.size || 0;
207
+ const bSize = b.metadata?.size || 0;
208
+ return bSize - aSize;
209
+ default:
210
+ return 0;
211
+ }
212
+ });
213
+ // Recursively sort children
214
+ for (const child of node.children) {
215
+ this.sortTreeNodes(child, sortBy);
216
+ }
217
+ }
218
+ static limitDepth(node, maxDepth, currentDepth = 0) {
219
+ if (currentDepth >= maxDepth) {
220
+ delete node.children;
221
+ return;
222
+ }
223
+ if (node.children) {
224
+ for (const child of node.children) {
225
+ this.limitDepth(child, maxDepth, currentDepth + 1);
226
+ }
227
+ }
228
+ }
229
+ /**
230
+ * Validate tree structure - ensures no recursion
231
+ */
232
+ static validateTree(node, visited = new Set()) {
233
+ const errors = [];
234
+ // Check for cycles
235
+ if (visited.has(node.path)) {
236
+ errors.push(`Cycle detected at path: ${node.path}`);
237
+ return { valid: false, errors };
238
+ }
239
+ visited.add(node.path);
240
+ // Check children
241
+ if (node.children) {
242
+ const childPaths = new Set();
243
+ for (const child of node.children) {
244
+ // Check for duplicate children
245
+ if (childPaths.has(child.path)) {
246
+ errors.push(`Duplicate child path: ${child.path}`);
247
+ }
248
+ childPaths.add(child.path);
249
+ // Check child is not parent
250
+ if (child.path === node.path) {
251
+ errors.push(`Directory contains itself: ${node.path}`);
252
+ }
253
+ // Check child is actually under parent
254
+ if (node.path !== '/' && !child.path.startsWith(node.path + '/')) {
255
+ errors.push(`Child ${child.path} not under parent ${node.path}`);
256
+ }
257
+ // Recursively validate children
258
+ const childValidation = this.validateTree(child, new Set(visited));
259
+ errors.push(...childValidation.errors);
260
+ }
261
+ }
262
+ return {
263
+ valid: errors.length === 0,
264
+ errors
265
+ };
266
+ }
267
+ }
268
+ //# sourceMappingURL=TreeUtils.js.map
@@ -52,6 +52,37 @@ export declare class VirtualFileSystem implements IVirtualFileSystem {
52
52
  * Delete a file
53
53
  */
54
54
  unlink(path: string): Promise<void>;
55
+ /**
56
+ * Get only direct children of a directory - guaranteed no self-inclusion
57
+ * This is the SAFE way to get children for building tree UIs
58
+ */
59
+ getDirectChildren(path: string): Promise<VFSEntity[]>;
60
+ /**
61
+ * Get a properly structured tree for the given path
62
+ * This prevents recursion issues common when building file explorers
63
+ */
64
+ getTreeStructure(path: string, options?: {
65
+ maxDepth?: number;
66
+ includeHidden?: boolean;
67
+ sort?: 'name' | 'modified' | 'size';
68
+ }): Promise<any>;
69
+ /**
70
+ * Get all descendants of a directory (flat list)
71
+ */
72
+ getDescendants(path: string, options?: {
73
+ includeAncestor?: boolean;
74
+ type?: 'file' | 'directory';
75
+ }): Promise<VFSEntity[]>;
76
+ /**
77
+ * Inspect a path and return structured information
78
+ * This is the recommended method for file explorers to use
79
+ */
80
+ inspect(path: string): Promise<{
81
+ node: VFSEntity;
82
+ children: VFSEntity[];
83
+ parent: VFSEntity | null;
84
+ stats: VFSStats;
85
+ }>;
55
86
  /**
56
87
  * Create a directory
57
88
  */
@@ -322,6 +322,116 @@ export class VirtualFileSystem {
322
322
  this.triggerWatchers(path, 'rename');
323
323
  // Knowledge Layer hooks will be added by augmentation if enabled
324
324
  }
325
+ // ============= Tree Operations (NEW) =============
326
+ /**
327
+ * Get only direct children of a directory - guaranteed no self-inclusion
328
+ * This is the SAFE way to get children for building tree UIs
329
+ */
330
+ async getDirectChildren(path) {
331
+ await this.ensureInitialized();
332
+ const entityId = await this.pathResolver.resolve(path);
333
+ const entity = await this.getEntityById(entityId);
334
+ // Verify it's a directory
335
+ if (entity.metadata.vfsType !== 'directory') {
336
+ throw new VFSError(VFSErrorCode.ENOTDIR, `Not a directory: ${path}`, path, 'getDirectChildren');
337
+ }
338
+ // Use the safe getChildren from PathResolver
339
+ const children = await this.pathResolver.getChildren(entityId);
340
+ // Double-check no self-inclusion (paranoid safety)
341
+ return children.filter(child => child.metadata.path !== path);
342
+ }
343
+ /**
344
+ * Get a properly structured tree for the given path
345
+ * This prevents recursion issues common when building file explorers
346
+ */
347
+ async getTreeStructure(path, options) {
348
+ await this.ensureInitialized();
349
+ const { VFSTreeUtils } = await import('./TreeUtils.js');
350
+ const entityId = await this.pathResolver.resolve(path);
351
+ const entity = await this.getEntityById(entityId);
352
+ if (entity.metadata.vfsType !== 'directory') {
353
+ throw new VFSError(VFSErrorCode.ENOTDIR, `Not a directory: ${path}`, path, 'getTreeStructure');
354
+ }
355
+ // Recursively gather all descendants
356
+ const allEntities = [];
357
+ const visited = new Set();
358
+ const gatherDescendants = async (dirId) => {
359
+ if (visited.has(dirId))
360
+ return; // Prevent cycles
361
+ visited.add(dirId);
362
+ const children = await this.pathResolver.getChildren(dirId);
363
+ for (const child of children) {
364
+ allEntities.push(child);
365
+ if (child.metadata.vfsType === 'directory') {
366
+ await gatherDescendants(child.id);
367
+ }
368
+ }
369
+ };
370
+ await gatherDescendants(entityId);
371
+ // Build safe tree structure
372
+ return VFSTreeUtils.buildTree(allEntities, path, options || {});
373
+ }
374
+ /**
375
+ * Get all descendants of a directory (flat list)
376
+ */
377
+ async getDescendants(path, options) {
378
+ await this.ensureInitialized();
379
+ const entityId = await this.pathResolver.resolve(path);
380
+ const entity = await this.getEntityById(entityId);
381
+ if (entity.metadata.vfsType !== 'directory') {
382
+ throw new VFSError(VFSErrorCode.ENOTDIR, `Not a directory: ${path}`, path, 'getDescendants');
383
+ }
384
+ const descendants = [];
385
+ if (options?.includeAncestor) {
386
+ descendants.push(entity);
387
+ }
388
+ const visited = new Set();
389
+ const queue = [entityId];
390
+ while (queue.length > 0) {
391
+ const currentId = queue.shift();
392
+ if (visited.has(currentId))
393
+ continue;
394
+ visited.add(currentId);
395
+ const children = await this.pathResolver.getChildren(currentId);
396
+ for (const child of children) {
397
+ // Filter by type if specified
398
+ if (!options?.type || child.metadata.vfsType === options.type) {
399
+ descendants.push(child);
400
+ }
401
+ // Add directories to queue for traversal
402
+ if (child.metadata.vfsType === 'directory') {
403
+ queue.push(child.id);
404
+ }
405
+ }
406
+ }
407
+ return descendants;
408
+ }
409
+ /**
410
+ * Inspect a path and return structured information
411
+ * This is the recommended method for file explorers to use
412
+ */
413
+ async inspect(path) {
414
+ await this.ensureInitialized();
415
+ const entityId = await this.pathResolver.resolve(path);
416
+ const entity = await this.getEntityById(entityId);
417
+ const stats = await this.stat(path);
418
+ let children = [];
419
+ if (entity.metadata.vfsType === 'directory') {
420
+ children = await this.getDirectChildren(path);
421
+ }
422
+ let parent = null;
423
+ if (path !== '/') {
424
+ const parentPath = path.substring(0, path.lastIndexOf('/')) || '/';
425
+ const parentId = await this.pathResolver.resolve(parentPath);
426
+ parent = await this.getEntityById(parentId);
427
+ }
428
+ return {
429
+ node: entity,
430
+ children,
431
+ parent,
432
+ stats
433
+ };
434
+ }
325
435
  // ============= Directory Operations =============
326
436
  /**
327
437
  * Create a directory
@@ -1364,37 +1474,29 @@ export class VirtualFileSystem {
1364
1474
  await this.ensureInitialized();
1365
1475
  const entityId = await this.pathResolver.resolve(path);
1366
1476
  const results = [];
1367
- // Get all relationships involving this entity (both from and to)
1477
+ // Use proper Brainy relationship API to get all relationships
1368
1478
  const [fromRelations, toRelations] = await Promise.all([
1369
- this.brain.find({
1370
- connected: {
1371
- from: entityId
1372
- },
1373
- limit: 1000
1374
- }),
1375
- this.brain.find({
1376
- connected: {
1377
- to: entityId
1378
- },
1379
- limit: 1000
1380
- })
1479
+ this.brain.getRelations({ from: entityId }),
1480
+ this.brain.getRelations({ to: entityId })
1381
1481
  ]);
1382
1482
  // Add outgoing relationships
1383
1483
  for (const rel of fromRelations) {
1384
- if (rel.entity.metadata?.path) {
1484
+ const targetEntity = await this.brain.get(rel.to);
1485
+ if (targetEntity && targetEntity.metadata?.path) {
1385
1486
  results.push({
1386
- path: rel.entity.metadata.path,
1387
- relationship: 'related',
1487
+ path: targetEntity.metadata.path,
1488
+ relationship: rel.type || 'related',
1388
1489
  direction: 'from'
1389
1490
  });
1390
1491
  }
1391
1492
  }
1392
1493
  // Add incoming relationships
1393
1494
  for (const rel of toRelations) {
1394
- if (rel.entity.metadata?.path) {
1495
+ const sourceEntity = await this.brain.get(rel.from);
1496
+ if (sourceEntity && sourceEntity.metadata?.path) {
1395
1497
  results.push({
1396
- path: rel.entity.metadata.path,
1397
- relationship: 'related',
1498
+ path: sourceEntity.metadata.path,
1499
+ relationship: rel.type || 'related',
1398
1500
  direction: 'to'
1399
1501
  });
1400
1502
  }
@@ -1405,49 +1507,37 @@ export class VirtualFileSystem {
1405
1507
  await this.ensureInitialized();
1406
1508
  const entityId = await this.pathResolver.resolve(path);
1407
1509
  const relationships = [];
1408
- // Get all relationships involving this entity (both from and to)
1510
+ // Use proper Brainy relationship API
1409
1511
  const [fromRelations, toRelations] = await Promise.all([
1410
- this.brain.find({
1411
- connected: {
1412
- from: entityId
1413
- },
1414
- limit: 1000
1415
- }),
1416
- this.brain.find({
1417
- connected: {
1418
- to: entityId
1419
- },
1420
- limit: 1000
1421
- })
1512
+ this.brain.getRelations({ from: entityId }),
1513
+ this.brain.getRelations({ to: entityId })
1422
1514
  ]);
1423
- // Add outgoing relationships (exclude parent-child relationships)
1515
+ // Process outgoing relationships (excluding Contains for parent-child)
1424
1516
  for (const rel of fromRelations) {
1425
- if (rel.entity.metadata?.path && rel.entity.metadata?.vfsType) {
1426
- // Skip parent-child relationships to focus on user-defined relationships
1427
- const parentPath = this.getParentPath(rel.entity.metadata.path);
1428
- if (parentPath !== path) { // Not a direct child
1517
+ if (rel.type !== VerbType.Contains) { // Skip filesystem hierarchy
1518
+ const targetEntity = await this.brain.get(rel.to);
1519
+ if (targetEntity && targetEntity.metadata?.path) {
1429
1520
  relationships.push({
1430
- id: crypto.randomUUID(),
1431
- from: path,
1432
- to: rel.entity.metadata.path,
1433
- type: VerbType.References,
1434
- createdAt: Date.now()
1521
+ id: rel.id || crypto.randomUUID(),
1522
+ from: entityId,
1523
+ to: rel.to,
1524
+ type: rel.type,
1525
+ createdAt: rel.createdAt || Date.now()
1435
1526
  });
1436
1527
  }
1437
1528
  }
1438
1529
  }
1439
- // Add incoming relationships (exclude parent-child relationships)
1530
+ // Process incoming relationships (excluding Contains for parent-child)
1440
1531
  for (const rel of toRelations) {
1441
- if (rel.entity.metadata?.path && rel.entity.metadata?.vfsType) {
1442
- // Skip parent-child relationships to focus on user-defined relationships
1443
- const parentPath = this.getParentPath(path);
1444
- if (rel.entity.metadata.path !== parentPath) { // Not the parent
1532
+ if (rel.type !== VerbType.Contains) { // Skip filesystem hierarchy
1533
+ const sourceEntity = await this.brain.get(rel.from);
1534
+ if (sourceEntity && sourceEntity.metadata?.path) {
1445
1535
  relationships.push({
1446
- id: crypto.randomUUID(),
1447
- from: rel.entity.metadata.path,
1448
- to: path,
1449
- type: VerbType.References,
1450
- createdAt: Date.now()
1536
+ id: rel.id || crypto.randomUUID(),
1537
+ from: rel.from,
1538
+ to: entityId,
1539
+ type: rel.type,
1540
+ createdAt: rel.createdAt || Date.now()
1451
1541
  });
1452
1542
  }
1453
1543
  }
@@ -283,6 +283,22 @@ export interface IVirtualFileSystem {
283
283
  recursive?: boolean;
284
284
  }): Promise<void>;
285
285
  readdir(path: string, options?: ReaddirOptions): Promise<string[] | VFSDirent[]>;
286
+ getDirectChildren(path: string): Promise<VFSEntity[]>;
287
+ getTreeStructure(path: string, options?: {
288
+ maxDepth?: number;
289
+ includeHidden?: boolean;
290
+ sort?: 'name' | 'modified' | 'size';
291
+ }): Promise<any>;
292
+ getDescendants(path: string, options?: {
293
+ includeAncestor?: boolean;
294
+ type?: 'file' | 'directory';
295
+ }): Promise<VFSEntity[]>;
296
+ inspect(path: string): Promise<{
297
+ node: VFSEntity;
298
+ children: VFSEntity[];
299
+ parent: VFSEntity | null;
300
+ stats: VFSStats;
301
+ }>;
286
302
  stat(path: string): Promise<VFSStats>;
287
303
  lstat(path: string): Promise<VFSStats>;
288
304
  exists(path: string): Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "3.10.1",
3
+ "version": "3.13.0",
4
4
  "description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns × 40 verbs for infinite expressiveness.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",