@soulcraft/brainy 3.12.0 → 3.14.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.
@@ -37,7 +37,29 @@ import type { BrainyAugmentation as BA, BaseAugmentation as BaseA, AugmentationC
37
37
  export type BrainyAugmentation = BA;
38
38
  export type BaseAugmentation = BaseA;
39
39
  export type AugmentationContext = AC;
40
+ /**
41
+ * @deprecated Use BrainyAugmentation from '../augmentations/brainyAugmentation.js' instead
42
+ *
43
+ * @example
44
+ * // ❌ Old way (v2.x):
45
+ * import { IAugmentation } from '@soulcraft/brainy/types/augmentations'
46
+ *
47
+ * // ✅ New way (v3.x):
48
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
49
+ */
40
50
  export type IAugmentation = BrainyAugmentation;
51
+ /**
52
+ * @deprecated Augmentation types are now unified under BrainyAugmentation interface
53
+ *
54
+ * @example
55
+ * // ❌ Old way (v2.x):
56
+ * import { AugmentationType } from '@soulcraft/brainy/types/augmentations'
57
+ * if (augmentation.type === AugmentationType.SENSE) { ... }
58
+ *
59
+ * // ✅ New way (v3.x):
60
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
61
+ * // Use the unified BrainyAugmentation interface directly
62
+ */
41
63
  export declare enum AugmentationType {
42
64
  SENSE = "sense",
43
65
  CONDUIT = "conduit",
@@ -49,23 +71,139 @@ export declare enum AugmentationType {
49
71
  WEBSOCKET = "webSocket",
50
72
  SYNAPSE = "synapse"
51
73
  }
74
+ /**
75
+ * @deprecated Use BrainyAugmentation interface directly instead of namespace types
76
+ *
77
+ * @example
78
+ * // ❌ Old way (v2.x):
79
+ * import { BrainyAugmentations } from '@soulcraft/brainy/types/augmentations'
80
+ * class MyAugmentation implements BrainyAugmentations.ISenseAugmentation { ... }
81
+ *
82
+ * // ✅ New way (v3.x):
83
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
84
+ * class MyAugmentation implements BrainyAugmentation { ... }
85
+ */
52
86
  export declare namespace BrainyAugmentations {
87
+ /** @deprecated Use BrainyAugmentation instead */
53
88
  type ISenseAugmentation = BrainyAugmentation;
89
+ /** @deprecated Use BrainyAugmentation instead */
54
90
  type IConduitAugmentation = BrainyAugmentation;
91
+ /** @deprecated Use BrainyAugmentation instead */
55
92
  type ICognitionAugmentation = BrainyAugmentation;
93
+ /** @deprecated Use BrainyAugmentation instead */
56
94
  type IMemoryAugmentation = BrainyAugmentation;
95
+ /** @deprecated Use BrainyAugmentation instead */
57
96
  type IPerceptionAugmentation = BrainyAugmentation;
97
+ /** @deprecated Use BrainyAugmentation instead */
58
98
  type IDialogAugmentation = BrainyAugmentation;
99
+ /** @deprecated Use BrainyAugmentation instead */
59
100
  type IActivationAugmentation = BrainyAugmentation;
101
+ /** @deprecated Use BrainyAugmentation instead */
60
102
  type ISynapseAugmentation = BrainyAugmentation;
61
103
  }
104
+ /**
105
+ * @deprecated Use BrainyAugmentation from '../augmentations/brainyAugmentation.js' instead
106
+ *
107
+ * @example
108
+ * // ❌ Old way (v2.x):
109
+ * import { ISenseAugmentation } from '@soulcraft/brainy/types/augmentations'
110
+ * class MySense implements ISenseAugmentation { ... }
111
+ *
112
+ * // ✅ New way (v3.x):
113
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
114
+ * class MySense implements BrainyAugmentation { ... }
115
+ */
62
116
  export type ISenseAugmentation = BrainyAugmentation;
117
+ /**
118
+ * @deprecated Use BrainyAugmentation from '../augmentations/brainyAugmentation.js' instead
119
+ *
120
+ * @example
121
+ * // ❌ Old way (v2.x):
122
+ * import { IConduitAugmentation } from '@soulcraft/brainy/types/augmentations'
123
+ *
124
+ * // ✅ New way (v3.x):
125
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
126
+ */
63
127
  export type IConduitAugmentation = BrainyAugmentation;
128
+ /**
129
+ * @deprecated Use BrainyAugmentation from '../augmentations/brainyAugmentation.js' instead
130
+ *
131
+ * @example
132
+ * // ❌ Old way (v2.x):
133
+ * import { ICognitionAugmentation } from '@soulcraft/brainy/types/augmentations'
134
+ *
135
+ * // ✅ New way (v3.x):
136
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
137
+ */
64
138
  export type ICognitionAugmentation = BrainyAugmentation;
139
+ /**
140
+ * @deprecated Use BrainyAugmentation from '../augmentations/brainyAugmentation.js' instead
141
+ *
142
+ * @example
143
+ * // ❌ Old way (v2.x):
144
+ * import { IMemoryAugmentation } from '@soulcraft/brainy/types/augmentations'
145
+ *
146
+ * // ✅ New way (v3.x):
147
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
148
+ */
65
149
  export type IMemoryAugmentation = BrainyAugmentation;
150
+ /**
151
+ * @deprecated Use BrainyAugmentation from '../augmentations/brainyAugmentation.js' instead
152
+ *
153
+ * @example
154
+ * // ❌ Old way (v2.x):
155
+ * import { IPerceptionAugmentation } from '@soulcraft/brainy/types/augmentations'
156
+ *
157
+ * // ✅ New way (v3.x):
158
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
159
+ */
66
160
  export type IPerceptionAugmentation = BrainyAugmentation;
161
+ /**
162
+ * @deprecated Use BrainyAugmentation from '../augmentations/brainyAugmentation.js' instead
163
+ *
164
+ * @example
165
+ * // ❌ Old way (v2.x):
166
+ * import { IDialogAugmentation } from '@soulcraft/brainy/types/augmentations'
167
+ *
168
+ * // ✅ New way (v3.x):
169
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
170
+ */
67
171
  export type IDialogAugmentation = BrainyAugmentation;
172
+ /**
173
+ * @deprecated Use BrainyAugmentation from '../augmentations/brainyAugmentation.js' instead
174
+ *
175
+ * @example
176
+ * // ❌ Old way (v2.x):
177
+ * import { IActivationAugmentation } from '@soulcraft/brainy/types/augmentations'
178
+ *
179
+ * // ✅ New way (v3.x):
180
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
181
+ */
68
182
  export type IActivationAugmentation = BrainyAugmentation;
183
+ /**
184
+ * @deprecated Use BrainyAugmentation from '../augmentations/brainyAugmentation.js' instead
185
+ *
186
+ * @example
187
+ * // ❌ Old way (v2.x):
188
+ * import { ISynapseAugmentation } from '@soulcraft/brainy/types/augmentations'
189
+ *
190
+ * // ✅ New way (v3.x):
191
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
192
+ */
69
193
  export type ISynapseAugmentation = BrainyAugmentation;
194
+ /**
195
+ * @deprecated WebSocket support is now built into BrainyAugmentation interface
196
+ *
197
+ * @example
198
+ * // ❌ Old way (v2.x):
199
+ * import { IWebSocketSupport } from '@soulcraft/brainy/types/augmentations'
200
+ * class MyAugmentation implements IWebSocketSupport { ... }
201
+ *
202
+ * // ✅ New way (v3.x):
203
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
204
+ * class MyAugmentation implements BrainyAugmentation {
205
+ * // WebSocket functionality is now part of the unified interface
206
+ * }
207
+ */
70
208
  export interface IWebSocketSupport {
71
209
  }
@@ -4,6 +4,18 @@
4
4
  * This file contains only the minimal types needed for augmentations.
5
5
  * The main augmentation interfaces are now in augmentations/brainyAugmentation.ts
6
6
  */
7
+ /**
8
+ * @deprecated Augmentation types are now unified under BrainyAugmentation interface
9
+ *
10
+ * @example
11
+ * // ❌ Old way (v2.x):
12
+ * import { AugmentationType } from '@soulcraft/brainy/types/augmentations'
13
+ * if (augmentation.type === AugmentationType.SENSE) { ... }
14
+ *
15
+ * // ✅ New way (v3.x):
16
+ * import { BrainyAugmentation } from '@soulcraft/brainy'
17
+ * // Use the unified BrainyAugmentation interface directly
18
+ */
7
19
  export var AugmentationType;
8
20
  (function (AugmentationType) {
9
21
  AugmentationType["SENSE"] = "sense";
@@ -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