@soulcraft/brainy 6.2.2 → 6.2.3
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/dist/augmentations/KnowledgeAugmentation.d.ts +40 -0
- package/dist/augmentations/KnowledgeAugmentation.js +251 -0
- package/dist/importManager.d.ts +78 -0
- package/dist/importManager.js +267 -0
- package/dist/query/typeInference.d.ts +158 -0
- package/dist/query/typeInference.js +760 -0
- package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +252 -0
- package/dist/storage/adapters/typeAwareStorageAdapter.js +814 -0
- package/dist/storage/baseStorage.d.ts +12 -0
- package/dist/storage/baseStorage.js +16 -0
- package/dist/types/brainyDataInterface.d.ts +52 -0
- package/dist/types/brainyDataInterface.js +10 -0
- package/dist/utils/metadataIndex.d.ts +6 -2
- package/dist/utils/metadataIndex.js +31 -14
- package/dist/vfs/ConceptSystem.d.ts +203 -0
- package/dist/vfs/ConceptSystem.js +545 -0
- package/dist/vfs/EntityManager.d.ts +75 -0
- package/dist/vfs/EntityManager.js +216 -0
- package/dist/vfs/EventRecorder.d.ts +84 -0
- package/dist/vfs/EventRecorder.js +269 -0
- package/dist/vfs/GitBridge.d.ts +167 -0
- package/dist/vfs/GitBridge.js +537 -0
- package/dist/vfs/KnowledgeLayer.d.ts +35 -0
- package/dist/vfs/KnowledgeLayer.js +443 -0
- package/dist/vfs/PersistentEntitySystem.d.ts +165 -0
- package/dist/vfs/PersistentEntitySystem.js +503 -0
- package/dist/vfs/SemanticVersioning.d.ts +105 -0
- package/dist/vfs/SemanticVersioning.js +309 -0
- package/package.json +1 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EntityManager Base Class
|
|
3
|
+
*
|
|
4
|
+
* Provides standardized entity ID management for all Knowledge Layer components
|
|
5
|
+
* Solves the root cause of ID mismatch issues by establishing clear patterns
|
|
6
|
+
*/
|
|
7
|
+
import { v4 as uuidv4 } from '../universal/uuid.js';
|
|
8
|
+
/**
|
|
9
|
+
* EntityManager Base Class
|
|
10
|
+
*
|
|
11
|
+
* All Knowledge Layer components should extend this to get standardized:
|
|
12
|
+
* - Entity storage and retrieval
|
|
13
|
+
* - ID management and mapping
|
|
14
|
+
* - Query patterns
|
|
15
|
+
* - Relationship creation
|
|
16
|
+
*/
|
|
17
|
+
export class EntityManager {
|
|
18
|
+
constructor(brain, systemName) {
|
|
19
|
+
this.brain = brain;
|
|
20
|
+
this.systemName = systemName;
|
|
21
|
+
this.idMappings = new Map(); // domainId -> brainyId
|
|
22
|
+
this.brainyToMappings = new Map(); // brainyId -> domainId
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Store an entity with proper ID management
|
|
26
|
+
*/
|
|
27
|
+
async storeEntity(entity, nounType, embedding, data) {
|
|
28
|
+
// Generate domain ID if not provided
|
|
29
|
+
if (!entity.id) {
|
|
30
|
+
entity.id = uuidv4();
|
|
31
|
+
}
|
|
32
|
+
// Store in Brainy and get the Brainy entity ID
|
|
33
|
+
const brainyId = await this.brain.add({
|
|
34
|
+
type: nounType,
|
|
35
|
+
data: data || Buffer.from(JSON.stringify(entity)),
|
|
36
|
+
metadata: {
|
|
37
|
+
...entity,
|
|
38
|
+
system: this.systemName,
|
|
39
|
+
domainId: entity.id, // Store the domain ID for reference
|
|
40
|
+
storedAt: Date.now()
|
|
41
|
+
},
|
|
42
|
+
vector: embedding
|
|
43
|
+
});
|
|
44
|
+
// Store ID mapping
|
|
45
|
+
this.idMappings.set(entity.id, brainyId);
|
|
46
|
+
this.brainyToMappings.set(brainyId, entity.id);
|
|
47
|
+
// Update entity with Brainy ID
|
|
48
|
+
entity.brainyId = brainyId;
|
|
49
|
+
return entity.id; // Return domain ID for external use
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Update an existing entity
|
|
53
|
+
*/
|
|
54
|
+
async updateEntity(entity, embedding) {
|
|
55
|
+
const brainyId = this.idMappings.get(entity.id);
|
|
56
|
+
if (!brainyId) {
|
|
57
|
+
throw new Error(`Entity ${entity.id} not found in mappings`);
|
|
58
|
+
}
|
|
59
|
+
await this.brain.update({
|
|
60
|
+
id: brainyId,
|
|
61
|
+
data: Buffer.from(JSON.stringify(entity)),
|
|
62
|
+
metadata: {
|
|
63
|
+
...entity,
|
|
64
|
+
system: this.systemName,
|
|
65
|
+
domainId: entity.id,
|
|
66
|
+
updatedAt: Date.now()
|
|
67
|
+
},
|
|
68
|
+
vector: embedding
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Retrieve entity by domain ID
|
|
73
|
+
*/
|
|
74
|
+
async getEntity(domainId) {
|
|
75
|
+
// First try from cache
|
|
76
|
+
let brainyId = this.idMappings.get(domainId);
|
|
77
|
+
// If not in cache, search by domain ID in metadata
|
|
78
|
+
if (!brainyId) {
|
|
79
|
+
const results = await this.brain.find({
|
|
80
|
+
where: {
|
|
81
|
+
domainId,
|
|
82
|
+
system: this.systemName
|
|
83
|
+
},
|
|
84
|
+
limit: 1
|
|
85
|
+
});
|
|
86
|
+
if (results.length === 0) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
brainyId = results[0].entity.id;
|
|
90
|
+
// Cache the mapping
|
|
91
|
+
this.idMappings.set(domainId, brainyId);
|
|
92
|
+
this.brainyToMappings.set(brainyId, domainId);
|
|
93
|
+
}
|
|
94
|
+
// Get entity using Brainy ID
|
|
95
|
+
const brainyEntity = await this.brain.get(brainyId);
|
|
96
|
+
if (!brainyEntity) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
// Parse entity from metadata
|
|
100
|
+
const entity = brainyEntity.metadata;
|
|
101
|
+
entity.brainyId = brainyId;
|
|
102
|
+
return entity;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Find entities by metadata criteria
|
|
106
|
+
*/
|
|
107
|
+
async findEntities(criteria, nounType, limit = 100) {
|
|
108
|
+
const query = {
|
|
109
|
+
...criteria,
|
|
110
|
+
system: this.systemName
|
|
111
|
+
};
|
|
112
|
+
const results = await this.brain.find({
|
|
113
|
+
where: query,
|
|
114
|
+
type: nounType,
|
|
115
|
+
limit
|
|
116
|
+
});
|
|
117
|
+
const entities = [];
|
|
118
|
+
for (const result of results) {
|
|
119
|
+
const entity = result.entity.metadata;
|
|
120
|
+
entity.brainyId = result.entity.id;
|
|
121
|
+
// Update mappings cache
|
|
122
|
+
this.idMappings.set(entity.id, result.entity.id);
|
|
123
|
+
this.brainyToMappings.set(result.entity.id, entity.id);
|
|
124
|
+
entities.push(entity);
|
|
125
|
+
}
|
|
126
|
+
return entities;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Create relationship between entities using proper Brainy IDs
|
|
130
|
+
*/
|
|
131
|
+
async createRelationship(fromDomainId, toDomainId, relationshipType, metadata) {
|
|
132
|
+
// Get Brainy IDs for both entities
|
|
133
|
+
const fromBrainyId = await this.getBrainyId(fromDomainId);
|
|
134
|
+
const toBrainyId = await this.getBrainyId(toDomainId);
|
|
135
|
+
if (!fromBrainyId || !toBrainyId) {
|
|
136
|
+
throw new Error(`Cannot find Brainy IDs for relationship: ${fromDomainId} -> ${toDomainId}`);
|
|
137
|
+
}
|
|
138
|
+
// Create relationship using Brainy IDs
|
|
139
|
+
await this.brain.relate({
|
|
140
|
+
from: fromBrainyId,
|
|
141
|
+
to: toBrainyId,
|
|
142
|
+
type: relationshipType,
|
|
143
|
+
metadata
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get Brainy ID for a domain ID
|
|
148
|
+
*/
|
|
149
|
+
async getBrainyId(domainId) {
|
|
150
|
+
// Check cache first
|
|
151
|
+
let brainyId = this.idMappings.get(domainId);
|
|
152
|
+
if (!brainyId) {
|
|
153
|
+
// Search in Brainy
|
|
154
|
+
const results = await this.brain.find({
|
|
155
|
+
where: {
|
|
156
|
+
domainId,
|
|
157
|
+
system: this.systemName
|
|
158
|
+
},
|
|
159
|
+
limit: 1
|
|
160
|
+
});
|
|
161
|
+
if (results.length > 0) {
|
|
162
|
+
brainyId = results[0].entity.id;
|
|
163
|
+
// Cache the mapping
|
|
164
|
+
this.idMappings.set(domainId, brainyId);
|
|
165
|
+
this.brainyToMappings.set(brainyId, domainId);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return brainyId || null;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get domain ID for a Brainy ID
|
|
172
|
+
*/
|
|
173
|
+
getDomainId(brainyId) {
|
|
174
|
+
return this.brainyToMappings.get(brainyId) || null;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Delete entity by domain ID
|
|
178
|
+
*/
|
|
179
|
+
async deleteEntity(domainId) {
|
|
180
|
+
const brainyId = await this.getBrainyId(domainId);
|
|
181
|
+
if (brainyId) {
|
|
182
|
+
await this.brain.delete(brainyId);
|
|
183
|
+
// Clean up mappings
|
|
184
|
+
this.idMappings.delete(domainId);
|
|
185
|
+
this.brainyToMappings.delete(brainyId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Clear ID mapping cache (useful for tests)
|
|
190
|
+
*/
|
|
191
|
+
clearMappingCache() {
|
|
192
|
+
this.idMappings.clear();
|
|
193
|
+
this.brainyToMappings.clear();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Batch load mappings for performance
|
|
197
|
+
*/
|
|
198
|
+
async loadMappings(domainIds) {
|
|
199
|
+
const unknownIds = domainIds.filter(id => !this.idMappings.has(id));
|
|
200
|
+
if (unknownIds.length === 0)
|
|
201
|
+
return;
|
|
202
|
+
const results = await this.brain.find({
|
|
203
|
+
where: {
|
|
204
|
+
domainId: { $in: unknownIds },
|
|
205
|
+
system: this.systemName
|
|
206
|
+
},
|
|
207
|
+
limit: unknownIds.length
|
|
208
|
+
});
|
|
209
|
+
for (const result of results) {
|
|
210
|
+
const domainId = result.entity.metadata.domainId;
|
|
211
|
+
this.idMappings.set(domainId, result.entity.id);
|
|
212
|
+
this.brainyToMappings.set(result.entity.id, domainId);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=EntityManager.js.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Recording System for VFS
|
|
3
|
+
*
|
|
4
|
+
* Records every file operation as an event for complete history tracking
|
|
5
|
+
* PRODUCTION-READY: No mocks, real implementation
|
|
6
|
+
*/
|
|
7
|
+
import { Brainy } from '../brainy.js';
|
|
8
|
+
import { EntityManager, ManagedEntity } from './EntityManager.js';
|
|
9
|
+
/**
|
|
10
|
+
* File operation event
|
|
11
|
+
*/
|
|
12
|
+
export interface FileEvent extends ManagedEntity {
|
|
13
|
+
id: string;
|
|
14
|
+
type: 'create' | 'write' | 'append' | 'delete' | 'move' | 'rename' | 'mkdir' | 'rmdir';
|
|
15
|
+
path: string;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
content?: Buffer;
|
|
18
|
+
size?: number;
|
|
19
|
+
hash?: string;
|
|
20
|
+
author?: string;
|
|
21
|
+
metadata?: Record<string, any>;
|
|
22
|
+
previousHash?: string;
|
|
23
|
+
oldPath?: string;
|
|
24
|
+
eventType?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Event Recorder - Stores all file operations as searchable events
|
|
28
|
+
*/
|
|
29
|
+
export declare class EventRecorder extends EntityManager {
|
|
30
|
+
constructor(brain: Brainy);
|
|
31
|
+
/**
|
|
32
|
+
* Record a file operation event
|
|
33
|
+
*/
|
|
34
|
+
recordEvent(event: Omit<FileEvent, 'id' | 'timestamp'>): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Get all events matching criteria
|
|
37
|
+
*/
|
|
38
|
+
getEvents(options?: {
|
|
39
|
+
since?: number;
|
|
40
|
+
until?: number;
|
|
41
|
+
types?: string[];
|
|
42
|
+
limit?: number;
|
|
43
|
+
}): Promise<FileEvent[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Get complete history for a file
|
|
46
|
+
*/
|
|
47
|
+
getHistory(path: string, options?: {
|
|
48
|
+
limit?: number;
|
|
49
|
+
since?: number;
|
|
50
|
+
until?: number;
|
|
51
|
+
types?: FileEvent['type'][];
|
|
52
|
+
}): Promise<FileEvent[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Replay events to reconstruct file state at a specific time
|
|
55
|
+
*/
|
|
56
|
+
reconstructFileAtTime(path: string, timestamp: number): Promise<Buffer | null>;
|
|
57
|
+
/**
|
|
58
|
+
* Get file changes between two timestamps
|
|
59
|
+
*/
|
|
60
|
+
getChanges(since: number, until?: number): Promise<FileEvent[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Calculate statistics for a file or directory
|
|
63
|
+
*/
|
|
64
|
+
getStatistics(path: string): Promise<{
|
|
65
|
+
totalEvents: number;
|
|
66
|
+
firstEvent: number | null;
|
|
67
|
+
lastEvent: number | null;
|
|
68
|
+
totalWrites: number;
|
|
69
|
+
totalBytes: number;
|
|
70
|
+
authors: string[];
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Find files that changed together (temporal coupling)
|
|
74
|
+
*/
|
|
75
|
+
findTemporalCoupling(path: string, windowMs?: number): Promise<Map<string, number>>;
|
|
76
|
+
/**
|
|
77
|
+
* Generate embedding for an event (for semantic search)
|
|
78
|
+
*/
|
|
79
|
+
private generateEventEmbedding;
|
|
80
|
+
/**
|
|
81
|
+
* Prune old events (for storage management)
|
|
82
|
+
*/
|
|
83
|
+
pruneEvents(olderThan: number, keepEvery?: number): Promise<number>;
|
|
84
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Recording System for VFS
|
|
3
|
+
*
|
|
4
|
+
* Records every file operation as an event for complete history tracking
|
|
5
|
+
* PRODUCTION-READY: No mocks, real implementation
|
|
6
|
+
*/
|
|
7
|
+
import { NounType } from '../types/graphTypes.js';
|
|
8
|
+
import { v4 as uuidv4 } from '../universal/uuid.js';
|
|
9
|
+
import { createHash } from 'crypto';
|
|
10
|
+
import { EntityManager } from './EntityManager.js';
|
|
11
|
+
/**
|
|
12
|
+
* Event Recorder - Stores all file operations as searchable events
|
|
13
|
+
*/
|
|
14
|
+
export class EventRecorder extends EntityManager {
|
|
15
|
+
constructor(brain) {
|
|
16
|
+
super(brain, 'vfs');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Record a file operation event
|
|
20
|
+
*/
|
|
21
|
+
async recordEvent(event) {
|
|
22
|
+
const eventId = uuidv4();
|
|
23
|
+
const timestamp = Date.now();
|
|
24
|
+
// Calculate content hash if content provided
|
|
25
|
+
const hash = event.content
|
|
26
|
+
? createHash('sha256').update(event.content).digest('hex')
|
|
27
|
+
: undefined;
|
|
28
|
+
// Create file event
|
|
29
|
+
const fileEvent = {
|
|
30
|
+
id: eventId,
|
|
31
|
+
type: event.type,
|
|
32
|
+
path: event.path,
|
|
33
|
+
timestamp,
|
|
34
|
+
content: event.content,
|
|
35
|
+
size: event.size,
|
|
36
|
+
hash,
|
|
37
|
+
author: event.author,
|
|
38
|
+
metadata: event.metadata,
|
|
39
|
+
previousHash: event.previousHash,
|
|
40
|
+
oldPath: event.oldPath
|
|
41
|
+
};
|
|
42
|
+
// Generate embedding for content-based events
|
|
43
|
+
const embedding = event.content && event.content.length < 100000
|
|
44
|
+
? await this.generateEventEmbedding(event)
|
|
45
|
+
: undefined;
|
|
46
|
+
// Add eventType for event classification
|
|
47
|
+
const eventWithType = {
|
|
48
|
+
...fileEvent,
|
|
49
|
+
eventType: 'file-operation'
|
|
50
|
+
};
|
|
51
|
+
// Store event using EntityManager
|
|
52
|
+
await this.storeEntity(eventWithType, NounType.Event, embedding, event.content || Buffer.from(JSON.stringify(event)));
|
|
53
|
+
return eventId;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get all events matching criteria
|
|
57
|
+
*/
|
|
58
|
+
async getEvents(options) {
|
|
59
|
+
const limit = options?.limit || 100;
|
|
60
|
+
const since = options?.since || 0;
|
|
61
|
+
const until = options?.until || Date.now();
|
|
62
|
+
const query = {
|
|
63
|
+
eventType: 'file-operation'
|
|
64
|
+
};
|
|
65
|
+
// Add time filters
|
|
66
|
+
if (since || until) {
|
|
67
|
+
query.timestamp = {};
|
|
68
|
+
if (since)
|
|
69
|
+
query.timestamp.$gte = since;
|
|
70
|
+
if (until)
|
|
71
|
+
query.timestamp.$lte = until;
|
|
72
|
+
}
|
|
73
|
+
// Add type filter
|
|
74
|
+
if (options?.types && options.types.length > 0) {
|
|
75
|
+
query.type = { $in: options.types };
|
|
76
|
+
}
|
|
77
|
+
// Query using EntityManager
|
|
78
|
+
const events = await this.findEntities(query, NounType.Event, limit);
|
|
79
|
+
return events.sort((a, b) => b.timestamp - a.timestamp);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get complete history for a file
|
|
83
|
+
*/
|
|
84
|
+
async getHistory(path, options) {
|
|
85
|
+
const query = {
|
|
86
|
+
path,
|
|
87
|
+
eventType: 'file-operation'
|
|
88
|
+
};
|
|
89
|
+
// Add time filters
|
|
90
|
+
if (options?.since || options?.until) {
|
|
91
|
+
query.timestamp = {};
|
|
92
|
+
if (options.since)
|
|
93
|
+
query.timestamp.$gte = options.since;
|
|
94
|
+
if (options.until)
|
|
95
|
+
query.timestamp.$lte = options.until;
|
|
96
|
+
}
|
|
97
|
+
// Add type filter
|
|
98
|
+
if (options?.types && options.types.length > 0) {
|
|
99
|
+
query.type = { $in: options.types };
|
|
100
|
+
}
|
|
101
|
+
// Query using EntityManager
|
|
102
|
+
const events = await this.findEntities(query, NounType.Event, options?.limit || 100);
|
|
103
|
+
// Sort by timestamp (newest first)
|
|
104
|
+
return events.sort((a, b) => b.timestamp - a.timestamp);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Replay events to reconstruct file state at a specific time
|
|
108
|
+
*/
|
|
109
|
+
async reconstructFileAtTime(path, timestamp) {
|
|
110
|
+
// Get all events up to the specified time
|
|
111
|
+
const events = await this.getHistory(path, {
|
|
112
|
+
until: timestamp,
|
|
113
|
+
types: ['create', 'write', 'append', 'delete']
|
|
114
|
+
});
|
|
115
|
+
// Sort chronologically for replay
|
|
116
|
+
const chronological = events.reverse();
|
|
117
|
+
// Find last write or create event
|
|
118
|
+
let lastContent = null;
|
|
119
|
+
let deleted = false;
|
|
120
|
+
for (const event of chronological) {
|
|
121
|
+
switch (event.type) {
|
|
122
|
+
case 'create':
|
|
123
|
+
case 'write':
|
|
124
|
+
lastContent = event.content || null;
|
|
125
|
+
deleted = false;
|
|
126
|
+
break;
|
|
127
|
+
case 'append':
|
|
128
|
+
if (lastContent && event.content) {
|
|
129
|
+
lastContent = Buffer.concat([lastContent, event.content]);
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
case 'delete':
|
|
133
|
+
deleted = true;
|
|
134
|
+
lastContent = null;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return deleted ? null : lastContent;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get file changes between two timestamps
|
|
142
|
+
*/
|
|
143
|
+
async getChanges(since, until) {
|
|
144
|
+
const query = {
|
|
145
|
+
eventType: 'file-operation',
|
|
146
|
+
timestamp: { $gte: since }
|
|
147
|
+
};
|
|
148
|
+
if (until) {
|
|
149
|
+
query.timestamp.$lte = until;
|
|
150
|
+
}
|
|
151
|
+
// Query using EntityManager
|
|
152
|
+
const events = await this.findEntities(query, NounType.Event, 1000);
|
|
153
|
+
return events.map(event => ({
|
|
154
|
+
id: event.id,
|
|
155
|
+
type: event.type,
|
|
156
|
+
path: event.path,
|
|
157
|
+
timestamp: event.timestamp,
|
|
158
|
+
size: event.size,
|
|
159
|
+
hash: event.hash,
|
|
160
|
+
author: event.author
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Calculate statistics for a file or directory
|
|
165
|
+
*/
|
|
166
|
+
async getStatistics(path) {
|
|
167
|
+
const events = await this.getHistory(path, { limit: 10000 });
|
|
168
|
+
if (events.length === 0) {
|
|
169
|
+
return {
|
|
170
|
+
totalEvents: 0,
|
|
171
|
+
firstEvent: null,
|
|
172
|
+
lastEvent: null,
|
|
173
|
+
totalWrites: 0,
|
|
174
|
+
totalBytes: 0,
|
|
175
|
+
authors: []
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const stats = {
|
|
179
|
+
totalEvents: events.length,
|
|
180
|
+
firstEvent: events[events.length - 1].timestamp, // Oldest
|
|
181
|
+
lastEvent: events[0].timestamp, // Newest
|
|
182
|
+
totalWrites: 0,
|
|
183
|
+
totalBytes: 0,
|
|
184
|
+
authors: new Set()
|
|
185
|
+
};
|
|
186
|
+
for (const event of events) {
|
|
187
|
+
if (event.type === 'write' || event.type === 'append') {
|
|
188
|
+
stats.totalWrites++;
|
|
189
|
+
stats.totalBytes += event.size || 0;
|
|
190
|
+
}
|
|
191
|
+
if (event.author) {
|
|
192
|
+
stats.authors.add(event.author);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
...stats,
|
|
197
|
+
authors: Array.from(stats.authors)
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Find files that changed together (temporal coupling)
|
|
202
|
+
*/
|
|
203
|
+
async findTemporalCoupling(path, windowMs = 60000) {
|
|
204
|
+
const events = await this.getHistory(path);
|
|
205
|
+
const coupling = new Map();
|
|
206
|
+
for (const event of events) {
|
|
207
|
+
// Find other files changed within the time window
|
|
208
|
+
const related = await this.getChanges(event.timestamp - windowMs, event.timestamp + windowMs);
|
|
209
|
+
for (const relatedEvent of related) {
|
|
210
|
+
if (relatedEvent.path !== path) {
|
|
211
|
+
const count = coupling.get(relatedEvent.path) || 0;
|
|
212
|
+
coupling.set(relatedEvent.path, count + 1);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Sort by coupling strength
|
|
217
|
+
return new Map(Array.from(coupling.entries())
|
|
218
|
+
.sort((a, b) => b[1] - a[1]));
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Generate embedding for an event (for semantic search)
|
|
222
|
+
*/
|
|
223
|
+
async generateEventEmbedding(event) {
|
|
224
|
+
try {
|
|
225
|
+
// Generate embedding based on event type and content
|
|
226
|
+
let textToEmbed;
|
|
227
|
+
if (event.type === 'write' || event.type === 'create') {
|
|
228
|
+
// For write/create events with content, embed the content
|
|
229
|
+
if (event.content && event.content.length < 100000) {
|
|
230
|
+
// Convert Buffer to string for text files
|
|
231
|
+
textToEmbed = Buffer.isBuffer(event.content)
|
|
232
|
+
? event.content.toString('utf8', 0, Math.min(10240, event.content.length))
|
|
233
|
+
: String(event.content).slice(0, 10240);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// For large files or no content, create descriptive text
|
|
237
|
+
textToEmbed = `File ${event.type} event at ${event.path}, size: ${event.size || 0} bytes`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// For other events (read, delete, rename, etc), create descriptive text
|
|
242
|
+
textToEmbed = `File ${event.type} event at ${event.path}${event.oldPath ? ` from ${event.oldPath}` : ''}`;
|
|
243
|
+
}
|
|
244
|
+
// Use Brainy's embed function
|
|
245
|
+
const vector = await this.brain.embed(textToEmbed);
|
|
246
|
+
return vector;
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
console.error('Failed to generate event embedding:', error);
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Prune old events (for storage management)
|
|
255
|
+
*/
|
|
256
|
+
async pruneEvents(olderThan, keepEvery = 10) {
|
|
257
|
+
const events = await this.getChanges(0, Date.now() - olderThan);
|
|
258
|
+
let pruned = 0;
|
|
259
|
+
for (let i = 0; i < events.length; i++) {
|
|
260
|
+
// Keep every Nth event for history sampling
|
|
261
|
+
if (i % keepEvery !== 0) {
|
|
262
|
+
await this.deleteEntity(events[i].id);
|
|
263
|
+
pruned++;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return pruned;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=EventRecorder.js.map
|