@soulcraft/brainy 4.3.2 → 4.5.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.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Import Progress Tracker (v4.5.0)
3
+ *
4
+ * Comprehensive progress tracking for imports with:
5
+ * - Multi-dimensional progress (bytes, entities, stages, timing)
6
+ * - Smart estimation (entity count, time remaining)
7
+ * - Stage-specific metrics (bytes/sec vs entities/sec)
8
+ * - Throttled callbacks (avoid spam)
9
+ * - Weighted overall progress
10
+ *
11
+ * @since v4.5.0
12
+ */
13
+ import { ImportProgress, ImportStage, StageWeights, ImportProgressCallback } from '../types/brainy.types.js';
14
+ /**
15
+ * Progress tracker for imports
16
+ */
17
+ export declare class ImportProgressTracker {
18
+ private readonly stageWeights;
19
+ private readonly throttleMs;
20
+ private readonly callback?;
21
+ private startTime;
22
+ private lastEmitTime;
23
+ private currentStage;
24
+ private completedStages;
25
+ private totalBytes;
26
+ private bytesProcessed;
27
+ private entitiesExtracted;
28
+ private entitiesIndexed;
29
+ private parseStartTime?;
30
+ private extractStartTime?;
31
+ private indexStartTime?;
32
+ private lastBytesCheckpoint;
33
+ private lastBytesCheckpointTime;
34
+ private lastEntitiesCheckpoint;
35
+ private lastEntitiesCheckpointTime;
36
+ private currentItem?;
37
+ private currentFile?;
38
+ private fileNumber?;
39
+ private totalFiles?;
40
+ private peakMemoryMB;
41
+ constructor(options?: {
42
+ totalBytes?: number;
43
+ stageWeights?: Partial<StageWeights>;
44
+ throttleMs?: number;
45
+ callback?: ImportProgressCallback;
46
+ });
47
+ /**
48
+ * Set total file size (if known later)
49
+ */
50
+ setTotalBytes(bytes: number): void;
51
+ /**
52
+ * Update current stage
53
+ */
54
+ setStage(stage: ImportStage, message?: string): void;
55
+ /**
56
+ * Update bytes processed
57
+ */
58
+ updateBytes(bytes: number): void;
59
+ /**
60
+ * Increment bytes processed
61
+ */
62
+ addBytes(bytes: number): void;
63
+ /**
64
+ * Update entities extracted
65
+ */
66
+ updateEntitiesExtracted(count: number): void;
67
+ /**
68
+ * Increment entities extracted
69
+ */
70
+ addEntitiesExtracted(count: number): void;
71
+ /**
72
+ * Update entities indexed
73
+ */
74
+ updateEntitiesIndexed(count: number): void;
75
+ /**
76
+ * Increment entities indexed
77
+ */
78
+ addEntitiesIndexed(count: number): void;
79
+ /**
80
+ * Set context information
81
+ */
82
+ setContext(context: {
83
+ currentItem?: string;
84
+ currentFile?: string;
85
+ fileNumber?: number;
86
+ totalFiles?: number;
87
+ }): void;
88
+ /**
89
+ * Set stage message
90
+ */
91
+ private setStageMessage;
92
+ /**
93
+ * Calculate stage progress (0-100 within current stage)
94
+ */
95
+ private calculateStageProgress;
96
+ /**
97
+ * Calculate overall progress (0-100 weighted across all stages)
98
+ */
99
+ private calculateOverallProgress;
100
+ /**
101
+ * Calculate bytes per second
102
+ */
103
+ private calculateBytesPerSecond;
104
+ /**
105
+ * Calculate entities per second
106
+ */
107
+ private calculateEntitiesPerSecond;
108
+ /**
109
+ * Estimate total entities
110
+ */
111
+ private estimateTotalEntities;
112
+ /**
113
+ * Estimate remaining time
114
+ */
115
+ private estimateRemainingTime;
116
+ /**
117
+ * Get current memory usage
118
+ */
119
+ private getCurrentMemoryMB;
120
+ /**
121
+ * Build complete progress object
122
+ */
123
+ private buildProgress;
124
+ /**
125
+ * Emit progress (throttled)
126
+ */
127
+ private emit;
128
+ /**
129
+ * Force emit (for completion or critical updates)
130
+ */
131
+ forceEmit(): void;
132
+ /**
133
+ * Get current progress (without emitting)
134
+ */
135
+ getProgress(): ImportProgress;
136
+ /**
137
+ * Mark import as complete
138
+ */
139
+ complete(): ImportProgress;
140
+ }
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Import Progress Tracker (v4.5.0)
3
+ *
4
+ * Comprehensive progress tracking for imports with:
5
+ * - Multi-dimensional progress (bytes, entities, stages, timing)
6
+ * - Smart estimation (entity count, time remaining)
7
+ * - Stage-specific metrics (bytes/sec vs entities/sec)
8
+ * - Throttled callbacks (avoid spam)
9
+ * - Weighted overall progress
10
+ *
11
+ * @since v4.5.0
12
+ */
13
+ /**
14
+ * Default stage weights (reflect typical time distribution)
15
+ */
16
+ const DEFAULT_STAGE_WEIGHTS = {
17
+ detecting: 0.01, // 1% - very fast
18
+ reading: 0.05, // 5% - reading file
19
+ parsing: 0.10, // 10% - parsing structure
20
+ extracting: 0.60, // 60% - AI extraction (slowest!)
21
+ indexing: 0.20, // 20% - creating graph
22
+ completing: 0.04 // 4% - cleanup
23
+ };
24
+ /**
25
+ * Stage ordering for progress calculation
26
+ */
27
+ const STAGE_ORDER = [
28
+ 'detecting',
29
+ 'reading',
30
+ 'parsing',
31
+ 'extracting',
32
+ 'indexing',
33
+ 'completing'
34
+ ];
35
+ /**
36
+ * Progress tracker for imports
37
+ */
38
+ export class ImportProgressTracker {
39
+ constructor(options = {}) {
40
+ this.lastEmitTime = 0;
41
+ this.currentStage = 'detecting';
42
+ this.completedStages = new Set();
43
+ // Metrics
44
+ this.totalBytes = 0;
45
+ this.bytesProcessed = 0;
46
+ this.entitiesExtracted = 0;
47
+ this.entitiesIndexed = 0;
48
+ // Estimation
49
+ this.lastBytesCheckpoint = 0;
50
+ this.lastBytesCheckpointTime = 0;
51
+ this.lastEntitiesCheckpoint = 0;
52
+ this.lastEntitiesCheckpointTime = 0;
53
+ // Memory tracking
54
+ this.peakMemoryMB = 0;
55
+ this.stageWeights = { ...DEFAULT_STAGE_WEIGHTS, ...options.stageWeights };
56
+ this.throttleMs = options.throttleMs ?? 100; // 100ms default
57
+ this.callback = options.callback;
58
+ this.totalBytes = options.totalBytes ?? 0;
59
+ this.startTime = Date.now();
60
+ this.lastBytesCheckpointTime = this.startTime;
61
+ this.lastEntitiesCheckpointTime = this.startTime;
62
+ }
63
+ /**
64
+ * Set total file size (if known later)
65
+ */
66
+ setTotalBytes(bytes) {
67
+ this.totalBytes = bytes;
68
+ }
69
+ /**
70
+ * Update current stage
71
+ */
72
+ setStage(stage, message) {
73
+ // Mark previous stage as complete
74
+ if (this.currentStage !== stage) {
75
+ this.completedStages.add(this.currentStage);
76
+ }
77
+ this.currentStage = stage;
78
+ if (message) {
79
+ this.setStageMessage(message);
80
+ }
81
+ // Track stage start times
82
+ const now = Date.now();
83
+ switch (stage) {
84
+ case 'parsing':
85
+ this.parseStartTime = now;
86
+ break;
87
+ case 'extracting':
88
+ this.extractStartTime = now;
89
+ break;
90
+ case 'indexing':
91
+ this.indexStartTime = now;
92
+ break;
93
+ }
94
+ // Force emit on stage change
95
+ this.emit(true);
96
+ }
97
+ /**
98
+ * Update bytes processed
99
+ */
100
+ updateBytes(bytes) {
101
+ this.bytesProcessed = bytes;
102
+ this.emit();
103
+ }
104
+ /**
105
+ * Increment bytes processed
106
+ */
107
+ addBytes(bytes) {
108
+ this.bytesProcessed += bytes;
109
+ this.emit();
110
+ }
111
+ /**
112
+ * Update entities extracted
113
+ */
114
+ updateEntitiesExtracted(count) {
115
+ this.entitiesExtracted = count;
116
+ this.emit();
117
+ }
118
+ /**
119
+ * Increment entities extracted
120
+ */
121
+ addEntitiesExtracted(count) {
122
+ this.entitiesExtracted += count;
123
+ this.emit();
124
+ }
125
+ /**
126
+ * Update entities indexed
127
+ */
128
+ updateEntitiesIndexed(count) {
129
+ this.entitiesIndexed = count;
130
+ this.emit();
131
+ }
132
+ /**
133
+ * Increment entities indexed
134
+ */
135
+ addEntitiesIndexed(count) {
136
+ this.entitiesIndexed += count;
137
+ this.emit();
138
+ }
139
+ /**
140
+ * Set context information
141
+ */
142
+ setContext(context) {
143
+ if (context.currentItem !== undefined)
144
+ this.currentItem = context.currentItem;
145
+ if (context.currentFile !== undefined)
146
+ this.currentFile = context.currentFile;
147
+ if (context.fileNumber !== undefined)
148
+ this.fileNumber = context.fileNumber;
149
+ if (context.totalFiles !== undefined)
150
+ this.totalFiles = context.totalFiles;
151
+ this.emit();
152
+ }
153
+ /**
154
+ * Set stage message
155
+ */
156
+ setStageMessage(message) {
157
+ this.currentItem = message;
158
+ }
159
+ /**
160
+ * Calculate stage progress (0-100 within current stage)
161
+ */
162
+ calculateStageProgress() {
163
+ switch (this.currentStage) {
164
+ case 'detecting':
165
+ case 'completing':
166
+ // These are quick, assume 100% once started
167
+ return 100;
168
+ case 'reading':
169
+ case 'parsing':
170
+ // Use bytes as proxy for progress
171
+ if (this.totalBytes === 0)
172
+ return 0;
173
+ return Math.min(100, (this.bytesProcessed / this.totalBytes) * 100);
174
+ case 'extracting':
175
+ // Extraction progress is hard to estimate (AI is unpredictable)
176
+ // We can't reliably say % complete, so return 0
177
+ return 0;
178
+ case 'indexing':
179
+ // If we have estimated total entities, use that
180
+ if (this.entitiesExtracted > 0) {
181
+ return Math.min(100, (this.entitiesIndexed / this.entitiesExtracted) * 100);
182
+ }
183
+ return 0;
184
+ default:
185
+ return 0;
186
+ }
187
+ }
188
+ /**
189
+ * Calculate overall progress (0-100 weighted across all stages)
190
+ */
191
+ calculateOverallProgress() {
192
+ // Calculate progress of completed stages
193
+ let completedWeight = 0;
194
+ for (const stage of this.completedStages) {
195
+ completedWeight += this.stageWeights[stage];
196
+ }
197
+ // Calculate progress of current stage
198
+ const stageProgress = this.calculateStageProgress();
199
+ const currentStageContribution = this.stageWeights[this.currentStage] * (stageProgress / 100);
200
+ // Overall = completed stages + current stage contribution
201
+ const overall = (completedWeight + currentStageContribution) * 100;
202
+ return Math.min(100, Math.max(0, overall));
203
+ }
204
+ /**
205
+ * Calculate bytes per second
206
+ */
207
+ calculateBytesPerSecond() {
208
+ const now = Date.now();
209
+ const elapsed = now - this.lastBytesCheckpointTime;
210
+ // Need at least 1 second of data
211
+ if (elapsed < 1000)
212
+ return undefined;
213
+ const bytesDelta = this.bytesProcessed - this.lastBytesCheckpoint;
214
+ const bytesPerSec = (bytesDelta / elapsed) * 1000;
215
+ // Update checkpoint
216
+ this.lastBytesCheckpoint = this.bytesProcessed;
217
+ this.lastBytesCheckpointTime = now;
218
+ return bytesPerSec > 0 ? bytesPerSec : undefined;
219
+ }
220
+ /**
221
+ * Calculate entities per second
222
+ */
223
+ calculateEntitiesPerSecond() {
224
+ const now = Date.now();
225
+ const elapsed = now - this.lastEntitiesCheckpointTime;
226
+ // Need at least 1 second of data
227
+ if (elapsed < 1000)
228
+ return undefined;
229
+ // Use appropriate counter based on stage
230
+ const currentCount = this.currentStage === 'indexing'
231
+ ? this.entitiesIndexed
232
+ : this.entitiesExtracted;
233
+ const entitiesDelta = currentCount - this.lastEntitiesCheckpoint;
234
+ const entitiesPerSec = (entitiesDelta / elapsed) * 1000;
235
+ // Update checkpoint
236
+ this.lastEntitiesCheckpoint = currentCount;
237
+ this.lastEntitiesCheckpointTime = now;
238
+ return entitiesPerSec > 0 ? entitiesPerSec : undefined;
239
+ }
240
+ /**
241
+ * Estimate total entities
242
+ */
243
+ estimateTotalEntities() {
244
+ // Only estimate if we've processed some bytes and extracted some entities
245
+ if (this.bytesProcessed === 0 || this.entitiesExtracted === 0 || this.totalBytes === 0) {
246
+ return undefined;
247
+ }
248
+ // Estimate based on entities per byte
249
+ const bytesPercentage = this.bytesProcessed / this.totalBytes;
250
+ const estimatedTotal = Math.ceil(this.entitiesExtracted / bytesPercentage);
251
+ // Confidence increases with more data
252
+ const confidence = Math.min(0.95, bytesPercentage);
253
+ return { count: estimatedTotal, confidence };
254
+ }
255
+ /**
256
+ * Estimate remaining time
257
+ */
258
+ estimateRemainingTime() {
259
+ const now = Date.now();
260
+ const elapsed = now - this.startTime;
261
+ // Need at least 5 seconds of data for reasonable estimate
262
+ if (elapsed < 5000)
263
+ return undefined;
264
+ const overallProgress = this.calculateOverallProgress();
265
+ if (overallProgress === 0)
266
+ return undefined;
267
+ // Estimate total time based on current progress
268
+ const estimatedTotalMs = (elapsed / overallProgress) * 100;
269
+ const remainingMs = estimatedTotalMs - elapsed;
270
+ return remainingMs > 0 ? remainingMs : undefined;
271
+ }
272
+ /**
273
+ * Get current memory usage
274
+ */
275
+ getCurrentMemoryMB() {
276
+ if (typeof process === 'undefined' || !process.memoryUsage)
277
+ return undefined;
278
+ const usage = process.memoryUsage();
279
+ const currentMB = usage.heapUsed / 1024 / 1024;
280
+ // Track peak
281
+ this.peakMemoryMB = Math.max(this.peakMemoryMB, currentMB);
282
+ return currentMB;
283
+ }
284
+ /**
285
+ * Build complete progress object
286
+ */
287
+ buildProgress() {
288
+ const now = Date.now();
289
+ const elapsed = now - this.startTime;
290
+ const stageProgress = this.calculateStageProgress();
291
+ const overallProgress = this.calculateOverallProgress();
292
+ const bytesPerSec = this.calculateBytesPerSecond();
293
+ const entitiesPerSec = this.calculateEntitiesPerSecond();
294
+ const entityEstimate = this.estimateTotalEntities();
295
+ const remainingMs = this.estimateRemainingTime();
296
+ const currentMemoryMB = this.getCurrentMemoryMB();
297
+ // Determine overall status
298
+ let overallStatus;
299
+ if (overallProgress === 0) {
300
+ overallStatus = 'starting';
301
+ }
302
+ else if (overallProgress === 100) {
303
+ overallStatus = 'done';
304
+ }
305
+ else if (this.currentStage === 'completing') {
306
+ overallStatus = 'completing';
307
+ }
308
+ else {
309
+ overallStatus = 'processing';
310
+ }
311
+ // Stage message
312
+ let stageMessage;
313
+ if (this.currentItem) {
314
+ stageMessage = this.currentItem;
315
+ }
316
+ else {
317
+ // Default messages
318
+ switch (this.currentStage) {
319
+ case 'detecting':
320
+ stageMessage = 'Detecting file format...';
321
+ break;
322
+ case 'reading':
323
+ stageMessage = 'Reading file...';
324
+ break;
325
+ case 'parsing':
326
+ stageMessage = 'Parsing file structure...';
327
+ break;
328
+ case 'extracting':
329
+ stageMessage = 'Extracting entities using AI...';
330
+ break;
331
+ case 'indexing':
332
+ stageMessage = 'Creating graph nodes...';
333
+ break;
334
+ case 'completing':
335
+ stageMessage = 'Finalizing import...';
336
+ break;
337
+ default:
338
+ stageMessage = 'Processing...';
339
+ }
340
+ }
341
+ // Calculate bytes percentage
342
+ const bytesPercentage = this.totalBytes > 0
343
+ ? (this.bytesProcessed / this.totalBytes) * 100
344
+ : 0;
345
+ // Build metrics object
346
+ const metrics = {
347
+ parsing_rate_mbps: this.currentStage === 'parsing' && bytesPerSec
348
+ ? bytesPerSec / 1000000
349
+ : undefined,
350
+ extraction_rate_entities_per_sec: this.currentStage === 'extracting'
351
+ ? entitiesPerSec
352
+ : undefined,
353
+ indexing_rate_entities_per_sec: this.currentStage === 'indexing'
354
+ ? entitiesPerSec
355
+ : undefined,
356
+ memory_usage_mb: currentMemoryMB,
357
+ peak_memory_mb: this.peakMemoryMB > 0 ? this.peakMemoryMB : undefined
358
+ };
359
+ const progress = {
360
+ // Overall
361
+ overall_progress: overallProgress,
362
+ overall_status: overallStatus,
363
+ // Stage
364
+ stage: this.currentStage,
365
+ stage_progress: stageProgress,
366
+ stage_message: stageMessage,
367
+ // Bytes
368
+ bytes_processed: this.bytesProcessed,
369
+ total_bytes: this.totalBytes,
370
+ bytes_percentage: bytesPercentage,
371
+ bytes_per_second: bytesPerSec,
372
+ // Entities
373
+ entities_extracted: this.entitiesExtracted,
374
+ entities_indexed: this.entitiesIndexed,
375
+ entities_per_second: entitiesPerSec,
376
+ estimated_total_entities: entityEstimate?.count,
377
+ estimation_confidence: entityEstimate?.confidence,
378
+ // Timing
379
+ elapsed_ms: elapsed,
380
+ estimated_remaining_ms: remainingMs,
381
+ estimated_total_ms: remainingMs ? elapsed + remainingMs : undefined,
382
+ // Context
383
+ current_item: this.currentItem,
384
+ current_file: this.currentFile,
385
+ file_number: this.fileNumber,
386
+ total_files: this.totalFiles,
387
+ // Metrics
388
+ metrics,
389
+ // Backwards compatibility
390
+ current: this.entitiesIndexed,
391
+ total: entityEstimate?.count ?? 0
392
+ };
393
+ return progress;
394
+ }
395
+ /**
396
+ * Emit progress (throttled)
397
+ */
398
+ emit(force = false) {
399
+ if (!this.callback)
400
+ return;
401
+ const now = Date.now();
402
+ const timeSinceLastEmit = now - this.lastEmitTime;
403
+ // Throttle unless forced
404
+ if (!force && timeSinceLastEmit < this.throttleMs) {
405
+ return;
406
+ }
407
+ const progress = this.buildProgress();
408
+ // Handle both callback types (legacy and new)
409
+ if (this.callback.length === 2) {
410
+ // Legacy callback: (current, total) => void
411
+ ;
412
+ this.callback(progress.current, progress.total);
413
+ }
414
+ else {
415
+ // New callback: (progress: ImportProgress) => void
416
+ ;
417
+ this.callback(progress);
418
+ }
419
+ this.lastEmitTime = now;
420
+ }
421
+ /**
422
+ * Force emit (for completion or critical updates)
423
+ */
424
+ forceEmit() {
425
+ this.emit(true);
426
+ }
427
+ /**
428
+ * Get current progress (without emitting)
429
+ */
430
+ getProgress() {
431
+ return this.buildProgress();
432
+ }
433
+ /**
434
+ * Mark import as complete
435
+ */
436
+ complete() {
437
+ this.currentStage = 'completing';
438
+ this.completedStages.add('completing');
439
+ const progress = this.buildProgress();
440
+ this.forceEmit();
441
+ return progress;
442
+ }
443
+ }
444
+ //# sourceMappingURL=import-progress-tracker.js.map
@@ -156,14 +156,14 @@ export class PathResolver {
156
156
  * Uses proper graph relationships to traverse the tree
157
157
  */
158
158
  async getChildren(dirId) {
159
- // Use proper graph API to get all Contains relationships from this directory
159
+ // Production-ready: Use graph relationships (VFS creates these in mkdir/writeFile)
160
160
  const relations = await this.brain.getRelations({
161
161
  from: dirId,
162
162
  type: VerbType.Contains
163
163
  });
164
164
  const validChildren = [];
165
165
  const childNames = new Set();
166
- // Fetch all child entities
166
+ // Fetch all child entities via relationships
167
167
  for (const relation of relations) {
168
168
  const entity = await this.brain.get(relation.to);
169
169
  if (entity && entity.metadata?.vfsType && entity.metadata?.name) {