@soulcraft/brainy 6.1.0 → 6.2.1
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/CHANGELOG.md +271 -0
- package/dist/augmentations/KnowledgeAugmentation.d.ts +40 -0
- package/dist/augmentations/KnowledgeAugmentation.js +251 -0
- package/dist/brainy.d.ts +17 -13
- package/dist/brainy.js +172 -41
- package/dist/coreTypes.d.ts +12 -0
- package/dist/graph/graphAdjacencyIndex.d.ts +23 -0
- package/dist/graph/graphAdjacencyIndex.js +49 -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 +36 -0
- package/dist/storage/baseStorage.js +159 -4
- package/dist/storage/cow/binaryDataCodec.d.ts +13 -2
- package/dist/storage/cow/binaryDataCodec.js +15 -2
- package/dist/types/brainy.types.d.ts +1 -0
- package/dist/types/brainyDataInterface.d.ts +52 -0
- package/dist/types/brainyDataInterface.js +10 -0
- package/dist/utils/metadataIndex.d.ts +17 -0
- package/dist/utils/metadataIndex.js +63 -0
- 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/dist/vfs/VirtualFileSystem.d.ts +37 -2
- package/dist/vfs/VirtualFileSystem.js +105 -68
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,277 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [6.2.0](https://github.com/soulcraftlabs/brainy/compare/v6.1.0...v6.2.0) (2025-11-20)
|
|
6
|
+
|
|
7
|
+
### ⚡ Critical Performance Fix
|
|
8
|
+
|
|
9
|
+
**Fixed VFS tree operations on cloud storage (GCS, S3, Azure, R2, OPFS)**
|
|
10
|
+
|
|
11
|
+
**Issue:** Despite v6.1.0's PathResolver optimization, `vfs.getTreeStructure()` remained critically slow on cloud storage:
|
|
12
|
+
- **Workshop Production (GCS):** 5,304ms for tree with maxDepth=2
|
|
13
|
+
- **Root Cause:** Tree traversal made 111+ separate storage calls (one per directory)
|
|
14
|
+
- **Why v6.1.0 didn't help:** v6.1.0 optimized path→ID resolution, but tree traversal still called `getChildren()` 111+ times
|
|
15
|
+
|
|
16
|
+
**Architecture Fix:**
|
|
17
|
+
```
|
|
18
|
+
OLD (v6.1.0):
|
|
19
|
+
- For each directory: getChildren(dirId) → fetch entities → GCS call
|
|
20
|
+
- 111 directories = 111 GCS calls × 50ms = 5,550ms
|
|
21
|
+
|
|
22
|
+
NEW (v6.2.0):
|
|
23
|
+
1. Traverse graph in-memory to collect all IDs (GraphAdjacencyIndex)
|
|
24
|
+
2. Batch-fetch ALL entities in ONE storage call (brain.batchGet)
|
|
25
|
+
3. Build tree structure from fetched entities
|
|
26
|
+
|
|
27
|
+
Result: 111 storage calls → 1 storage call
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Performance (Production Measurement):**
|
|
31
|
+
- **GCS:** 5,304ms → ~100ms (**53x faster**)
|
|
32
|
+
- **FileSystem:** Already fast, minimal change
|
|
33
|
+
|
|
34
|
+
**Files Changed:**
|
|
35
|
+
- `src/vfs/VirtualFileSystem.ts:616-689` - New `gatherDescendants()` method
|
|
36
|
+
- `src/vfs/VirtualFileSystem.ts:691-728` - Updated `getTreeStructure()` to use batch fetch
|
|
37
|
+
- `src/vfs/VirtualFileSystem.ts:730-762` - Updated `getDescendants()` to use batch fetch
|
|
38
|
+
|
|
39
|
+
**Impact:**
|
|
40
|
+
- ✅ Workshop file explorer now loads instantly on GCS
|
|
41
|
+
- ✅ Clean architecture: one code path, no fallbacks
|
|
42
|
+
- ✅ Production-scale: uses in-memory graph + single batch fetch
|
|
43
|
+
- ✅ Works for ALL storage adapters (GCS, S3, Azure, R2, OPFS, FileSystem)
|
|
44
|
+
|
|
45
|
+
**Migration:** No code changes required - automatic performance improvement.
|
|
46
|
+
|
|
47
|
+
### 🚨 Critical Bug Fix: Blob Integrity Check Failures (PERMANENT FIX)
|
|
48
|
+
|
|
49
|
+
**Fixed blob integrity check failures on cloud storage using key-based dispatch (NO MORE GUESSING)**
|
|
50
|
+
|
|
51
|
+
**Issue:** Production users reported "Blob integrity check failed" errors when opening files from GCS:
|
|
52
|
+
- **Symptom:** Random file read failures with hash mismatch errors
|
|
53
|
+
- **Root Cause:** `wrapBinaryData()` tried to guess data type by parsing, causing compressed binary that happens to be valid UTF-8 + valid JSON to be stored as parsed objects instead of wrapped binary
|
|
54
|
+
- **Impact:** On read, `JSON.stringify(object)` !== original compressed bytes → hash mismatch → integrity failure
|
|
55
|
+
|
|
56
|
+
**The Guessing Problem (v5.10.1 - v6.1.0):**
|
|
57
|
+
```typescript
|
|
58
|
+
// FRAGILE: wrapBinaryData() tries to JSON.parse ALL buffers
|
|
59
|
+
wrapBinaryData(compressedBuffer) {
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(data.toString()) // ← Compressed data accidentally parses!
|
|
62
|
+
} catch {
|
|
63
|
+
return {_binary: true, data: base64}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// FAILURE PATH:
|
|
68
|
+
// 1. WRITE: hash(raw) → compress(raw) → wrapBinaryData(compressed)
|
|
69
|
+
// → compressed bytes accidentally parse as valid JSON
|
|
70
|
+
// → stored as parsed object instead of wrapped binary
|
|
71
|
+
// 2. READ: retrieve object → JSON.stringify(object) → decompress
|
|
72
|
+
// → different bytes than original compressed data
|
|
73
|
+
// → HASH MISMATCH → "Blob integrity check failed"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**The Permanent Solution (v6.2.0): Key-Based Dispatch**
|
|
77
|
+
|
|
78
|
+
Stop guessing! The key naming convention **IS** the explicit type contract:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// baseStorage.ts COW adapter (line 371-393)
|
|
82
|
+
put: async (key: string, data: Buffer): Promise<void> => {
|
|
83
|
+
// NO GUESSING - key format explicitly declares data type:
|
|
84
|
+
//
|
|
85
|
+
// JSON keys: 'ref:*', '*-meta:*'
|
|
86
|
+
// Binary keys: 'blob:*', 'commit:*', 'tree:*'
|
|
87
|
+
|
|
88
|
+
const obj = key.includes('-meta:') || key.startsWith('ref:')
|
|
89
|
+
? JSON.parse(data.toString()) // Metadata/refs: ALWAYS JSON
|
|
90
|
+
: { _binary: true, data: data.toString('base64') } // Blobs: ALWAYS binary
|
|
91
|
+
|
|
92
|
+
await this.writeObjectToPath(`_cow/${key}`, obj)
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Why This is Permanent:**
|
|
97
|
+
- ✅ **Zero guessing** - key explicitly declares type
|
|
98
|
+
- ✅ **Works for ANY compression** - gzip, zstd, brotli, future algorithms
|
|
99
|
+
- ✅ **Self-documenting** - code clearly shows intent
|
|
100
|
+
- ✅ **No heuristics** - no fragile first-byte checks or try/catch parsing
|
|
101
|
+
- ✅ **Single source of truth** - key naming convention is the contract
|
|
102
|
+
|
|
103
|
+
**Files Changed:**
|
|
104
|
+
- `src/storage/baseStorage.ts:371-393` - COW adapter uses key-based dispatch (NO MORE wrapBinaryData)
|
|
105
|
+
- `src/storage/cow/binaryDataCodec.ts:86-119` - Deprecated wrapBinaryData() with warnings
|
|
106
|
+
- `tests/unit/storage/cow/BlobStorage.test.ts:612-705` - Added 4 comprehensive regression tests
|
|
107
|
+
|
|
108
|
+
**Regression Tests Added:**
|
|
109
|
+
1. JSON-like compressed data (THE KILLER TEST CASE)
|
|
110
|
+
2. All key types dispatch correctly (blob, commit, tree)
|
|
111
|
+
3. Metadata keys handled correctly
|
|
112
|
+
4. Verify wrapBinaryData() never called on write path
|
|
113
|
+
|
|
114
|
+
**Impact:**
|
|
115
|
+
- ✅ **PERMANENT FIX** - eliminates blob integrity failures forever
|
|
116
|
+
- ✅ Works for ALL storage adapters (GCS, S3, Azure, R2, OPFS, FileSystem)
|
|
117
|
+
- ✅ Works for ALL compression algorithms
|
|
118
|
+
- ✅ Comprehensive regression tests prevent future regressions
|
|
119
|
+
- ✅ No performance cost (key.includes() is fast)
|
|
120
|
+
|
|
121
|
+
**Migration:** No action required - automatic fix for all blob operations.
|
|
122
|
+
|
|
123
|
+
### ⚡ Performance Fix: Removed Access Time Updates on Reads
|
|
124
|
+
|
|
125
|
+
**Fixed 50-100ms GCS write penalty on EVERY file/directory read**
|
|
126
|
+
|
|
127
|
+
**Issue:** Production GCS performance showed file reads taking significantly longer than expected:
|
|
128
|
+
- **Expected:** ~50ms for file read
|
|
129
|
+
- **Actual:** ~100-150ms for file read
|
|
130
|
+
- **Root Cause:** `updateAccessTime()` called on EVERY `readFile()` and `readdir()` operation
|
|
131
|
+
- **Impact:** Each access time update = 50-100ms GCS write operation + doubled GCS costs
|
|
132
|
+
|
|
133
|
+
**The Problem:**
|
|
134
|
+
```typescript
|
|
135
|
+
// OLD (v6.1.0):
|
|
136
|
+
async readFile(path: string): Promise<Buffer> {
|
|
137
|
+
const entity = await this.getEntityByPath(path)
|
|
138
|
+
await this.updateAccessTime(entityId) // ← 50-100ms GCS write!
|
|
139
|
+
return await this.blobStorage.read(blobHash)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async readdir(path: string): Promise<string[]> {
|
|
143
|
+
const entity = await this.getEntityByPath(path)
|
|
144
|
+
await this.updateAccessTime(entityId) // ← 50-100ms GCS write!
|
|
145
|
+
return children.map(child => child.metadata.name)
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Why Access Time Updates Are Harmful:**
|
|
150
|
+
1. **Performance:** 50-100ms penalty on cloud storage for EVERY read
|
|
151
|
+
2. **Cost:** Doubles GCS operation costs (read + write for every file access)
|
|
152
|
+
3. **Unnecessary:** Modern filesystems use `noatime` mount option for same reason
|
|
153
|
+
4. **Unused:** The `accessed` field was NEVER used in queries, filters, or application logic
|
|
154
|
+
|
|
155
|
+
**Solution (v6.2.0): Remove Completely**
|
|
156
|
+
|
|
157
|
+
Following modern filesystem best practices (Linux `noatime`, macOS default behavior):
|
|
158
|
+
- ✅ Removed `updateAccessTime()` call from `readFile()` (line 372)
|
|
159
|
+
- ✅ Removed `updateAccessTime()` call from `readdir()` (line 1002)
|
|
160
|
+
- ✅ Removed `updateAccessTime()` method entirely (lines 1355-1365)
|
|
161
|
+
- ✅ Field `accessed` still exists in metadata for backward compatibility (just won't update)
|
|
162
|
+
|
|
163
|
+
**Performance Impact (Production Scale):**
|
|
164
|
+
- **File reads:** 100-150ms → 50ms (**2-3x faster**)
|
|
165
|
+
- **Directory reads:** 100-150ms → 50ms (**2-3x faster**)
|
|
166
|
+
- **GCS costs:** ~50% reduction (eliminated write operation on every read)
|
|
167
|
+
- **FileSystem:** Minimal impact (already fast, but removes unnecessary disk I/O)
|
|
168
|
+
|
|
169
|
+
**Files Changed:**
|
|
170
|
+
- `src/vfs/VirtualFileSystem.ts:372-375` - Removed updateAccessTime() from readFile()
|
|
171
|
+
- `src/vfs/VirtualFileSystem.ts:1002-1006` - Removed updateAccessTime() from readdir()
|
|
172
|
+
- `src/vfs/VirtualFileSystem.ts:1355-1365` - Removed updateAccessTime() method
|
|
173
|
+
|
|
174
|
+
**Impact:**
|
|
175
|
+
- ✅ **2-3x faster reads** on cloud storage
|
|
176
|
+
- ✅ **~50% GCS cost reduction** (no write on every read)
|
|
177
|
+
- ✅ Follows modern filesystem best practices
|
|
178
|
+
- ✅ Backward compatible: field exists but won't update
|
|
179
|
+
- ✅ Works for ALL storage adapters (GCS, S3, Azure, R2, OPFS, FileSystem)
|
|
180
|
+
|
|
181
|
+
**Migration:** No action required - automatic performance improvement.
|
|
182
|
+
|
|
183
|
+
### ⚡ Performance Fix: Eliminated N+1 Patterns Across All APIs
|
|
184
|
+
|
|
185
|
+
**Fixed 8 N+1 patterns for 10-20x faster batch operations on cloud storage**
|
|
186
|
+
|
|
187
|
+
**Issue:** Multiple APIs loaded entities/relationships one-by-one instead of using batch operations:
|
|
188
|
+
- `find()`: 5 different code paths loaded entities individually
|
|
189
|
+
- `batchGet()` with vectors: Looped through individual `get()` calls
|
|
190
|
+
- `executeGraphSearch()`: Loaded connected entities one-by-one
|
|
191
|
+
- `relate()` duplicate checking: Loaded existing relationships one-by-one
|
|
192
|
+
- `deleteMany()`: Created separate transaction for each entity
|
|
193
|
+
|
|
194
|
+
**Root Cause:** Individual storage calls instead of batch operations → N × 50ms on GCS = severe latency
|
|
195
|
+
|
|
196
|
+
**Solution (v6.2.0): Comprehensive Batch Operations**
|
|
197
|
+
|
|
198
|
+
**1. Fixed `find()` method - 5 locations**
|
|
199
|
+
```typescript
|
|
200
|
+
// OLD: N separate storage calls
|
|
201
|
+
for (const id of pageIds) {
|
|
202
|
+
const entity = await this.get(id) // ❌ N×50ms on GCS
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// NEW: Single batch call
|
|
206
|
+
const entitiesMap = await this.batchGet(pageIds) // ✅ 1×50ms on GCS
|
|
207
|
+
for (const id of pageIds) {
|
|
208
|
+
const entity = entitiesMap.get(id)
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**2. Fixed `batchGet()` with vectors**
|
|
213
|
+
- **Added:** `storage.getNounBatch(ids)` method (baseStorage.ts:1986)
|
|
214
|
+
- Batch-loads vectors + metadata in parallel
|
|
215
|
+
- Eliminates N+1 when `includeVectors: true`
|
|
216
|
+
|
|
217
|
+
**3. Fixed `executeGraphSearch()`**
|
|
218
|
+
- Uses `batchGet()` for connected entities
|
|
219
|
+
- 20 entities: 1,000ms → 50ms (**20x faster**)
|
|
220
|
+
|
|
221
|
+
**4. Fixed `relate()` duplicate checking**
|
|
222
|
+
- **Added:** `storage.getVerbsBatch(ids)` method (baseStorage.ts:826)
|
|
223
|
+
- **Added:** `graphIndex.getVerbsBatchCached(ids)` method (graphAdjacencyIndex.ts:384)
|
|
224
|
+
- Batch-loads existing relationships with cache-aware loading
|
|
225
|
+
- 5 verbs: 250ms → 50ms (**5x faster**)
|
|
226
|
+
|
|
227
|
+
**5. Fixed `deleteMany()`**
|
|
228
|
+
- **Changed:** Batches deletes into chunks of 10
|
|
229
|
+
- Single transaction per chunk (atomic within chunk)
|
|
230
|
+
- 10 entities: 2,000ms → 200ms (**10x faster**)
|
|
231
|
+
- Proper error handling with `continueOnError` flag
|
|
232
|
+
|
|
233
|
+
**Performance Impact (Production GCS):**
|
|
234
|
+
|
|
235
|
+
| Operation | Before | After | Speedup |
|
|
236
|
+
|-----------|--------|-------|---------|
|
|
237
|
+
| find() with 10 results | 10×50ms = 500ms | 1×50ms = 50ms | **10x** |
|
|
238
|
+
| batchGet() with vectors (10 entities) | 10×50ms = 500ms | 1×50ms = 50ms | **10x** |
|
|
239
|
+
| executeGraphSearch() with 20 entities | 20×50ms = 1000ms | 1×50ms = 50ms | **20x** |
|
|
240
|
+
| relate() duplicate check (5 verbs) | 5×50ms = 250ms | 1×50ms = 50ms | **5x** |
|
|
241
|
+
| deleteMany() with 10 entities | 10 txns = 2000ms | 1 txn = 200ms | **10x** |
|
|
242
|
+
|
|
243
|
+
**Files Changed:**
|
|
244
|
+
- `src/brainy.ts:1682-1690` - find() location 1 (batch load)
|
|
245
|
+
- `src/brainy.ts:1713-1720` - find() location 2 (batch load)
|
|
246
|
+
- `src/brainy.ts:1820-1832` - find() location 3 (batch load filtered results)
|
|
247
|
+
- `src/brainy.ts:1845-1853` - find() location 4 (batch load paginated)
|
|
248
|
+
- `src/brainy.ts:1870-1878` - find() location 5 (batch load sorted)
|
|
249
|
+
- `src/brainy.ts:724-732` - batchGet() with vectors optimization
|
|
250
|
+
- `src/brainy.ts:1171-1183` - relate() duplicate check optimization
|
|
251
|
+
- `src/brainy.ts:2216-2310` - deleteMany() transaction batching
|
|
252
|
+
- `src/brainy.ts:4314-4325` - executeGraphSearch() batch load
|
|
253
|
+
- `src/storage/baseStorage.ts:1986-2045` - Added getNounBatch()
|
|
254
|
+
- `src/storage/baseStorage.ts:826-886` - Added getVerbsBatch()
|
|
255
|
+
- `src/graph/graphAdjacencyIndex.ts:384-413` - Added getVerbsBatchCached()
|
|
256
|
+
- `src/coreTypes.ts:721,743` - Added batch methods to StorageAdapter interface
|
|
257
|
+
- `src/types/brainy.types.ts:367` - Added continueOnError to DeleteManyParams
|
|
258
|
+
|
|
259
|
+
**Architecture:**
|
|
260
|
+
- ✅ **COW/fork/asOf**: All batch methods use `readBatchWithInheritance()`
|
|
261
|
+
- ✅ **All storage adapters**: Works with GCS, S3, Azure, R2, OPFS, FileSystem
|
|
262
|
+
- ✅ **Caching**: getVerbsBatchCached() checks UnifiedCache first
|
|
263
|
+
- ✅ **Transactions**: deleteMany() batches into atomic chunks
|
|
264
|
+
- ✅ **Error handling**: Proper error collection with continueOnError support
|
|
265
|
+
|
|
266
|
+
**Impact:**
|
|
267
|
+
- ✅ **10-20x faster** batch operations on cloud storage
|
|
268
|
+
- ✅ **50-90% cost reduction** (fewer storage API calls)
|
|
269
|
+
- ✅ Clean architecture - no fallbacks, no hacks
|
|
270
|
+
- ✅ Backward compatible - automatic performance improvement
|
|
271
|
+
|
|
272
|
+
**Migration:** No action required - automatic performance improvement.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
5
276
|
## [6.1.0](https://github.com/soulcraftlabs/brainy/compare/v6.0.2...v6.1.0) (2025-11-20)
|
|
6
277
|
|
|
7
278
|
### 🚀 Features
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Layer Augmentation for VFS
|
|
3
|
+
*
|
|
4
|
+
* Adds intelligent features to VFS without modifying core functionality:
|
|
5
|
+
* - Event recording for all operations
|
|
6
|
+
* - Semantic versioning based on content changes
|
|
7
|
+
* - Entity and concept extraction
|
|
8
|
+
* - Git bridge for import/export
|
|
9
|
+
*
|
|
10
|
+
* This is a TRUE augmentation - VFS works perfectly without it
|
|
11
|
+
*/
|
|
12
|
+
import { Brainy } from '../brainy.js';
|
|
13
|
+
import { BaseAugmentation } from './brainyAugmentation.js';
|
|
14
|
+
export declare class KnowledgeAugmentation extends BaseAugmentation {
|
|
15
|
+
name: string;
|
|
16
|
+
timing: 'after';
|
|
17
|
+
metadata: 'none';
|
|
18
|
+
operations: any;
|
|
19
|
+
priority: number;
|
|
20
|
+
constructor(config?: any);
|
|
21
|
+
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
|
22
|
+
private eventRecorder?;
|
|
23
|
+
private semanticVersioning?;
|
|
24
|
+
private entitySystem?;
|
|
25
|
+
private conceptSystem?;
|
|
26
|
+
private gitBridge?;
|
|
27
|
+
private originalMethods;
|
|
28
|
+
initialize(context: any): Promise<void>;
|
|
29
|
+
augment(brain: Brainy): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Wrap a VFS method to add Knowledge Layer functionality
|
|
32
|
+
*/
|
|
33
|
+
private wrapMethod;
|
|
34
|
+
/**
|
|
35
|
+
* Add Knowledge Layer methods to VFS
|
|
36
|
+
*/
|
|
37
|
+
private addKnowledgeMethods;
|
|
38
|
+
private isSemanticChange;
|
|
39
|
+
cleanup(brain: Brainy): Promise<void>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Layer Augmentation for VFS
|
|
3
|
+
*
|
|
4
|
+
* Adds intelligent features to VFS without modifying core functionality:
|
|
5
|
+
* - Event recording for all operations
|
|
6
|
+
* - Semantic versioning based on content changes
|
|
7
|
+
* - Entity and concept extraction
|
|
8
|
+
* - Git bridge for import/export
|
|
9
|
+
*
|
|
10
|
+
* This is a TRUE augmentation - VFS works perfectly without it
|
|
11
|
+
*/
|
|
12
|
+
import { BaseAugmentation } from './brainyAugmentation.js';
|
|
13
|
+
import { EventRecorder } from '../vfs/EventRecorder.js';
|
|
14
|
+
import { SemanticVersioning } from '../vfs/SemanticVersioning.js';
|
|
15
|
+
import { PersistentEntitySystem } from '../vfs/PersistentEntitySystem.js';
|
|
16
|
+
import { ConceptSystem } from '../vfs/ConceptSystem.js';
|
|
17
|
+
import { GitBridge } from '../vfs/GitBridge.js';
|
|
18
|
+
export class KnowledgeAugmentation extends BaseAugmentation {
|
|
19
|
+
constructor(config = {}) {
|
|
20
|
+
super(config);
|
|
21
|
+
this.name = 'knowledge';
|
|
22
|
+
this.timing = 'after'; // Process after VFS operations
|
|
23
|
+
this.metadata = 'none'; // No metadata access needed
|
|
24
|
+
this.operations = []; // VFS-specific augmentation, no operation interception
|
|
25
|
+
this.priority = 100; // Run last
|
|
26
|
+
this.originalMethods = new Map();
|
|
27
|
+
}
|
|
28
|
+
async execute(operation, params, next) {
|
|
29
|
+
// Pass through - this augmentation works at VFS level, not operation level
|
|
30
|
+
return await next();
|
|
31
|
+
}
|
|
32
|
+
async initialize(context) {
|
|
33
|
+
await this.augment(context.brain);
|
|
34
|
+
}
|
|
35
|
+
async augment(brain) {
|
|
36
|
+
// Only augment if VFS exists
|
|
37
|
+
const vfs = brain.vfs?.();
|
|
38
|
+
if (!vfs) {
|
|
39
|
+
console.warn('KnowledgeAugmentation: VFS not found, skipping');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Initialize Knowledge Layer components
|
|
43
|
+
this.eventRecorder = new EventRecorder(brain);
|
|
44
|
+
this.semanticVersioning = new SemanticVersioning(brain);
|
|
45
|
+
this.entitySystem = new PersistentEntitySystem(brain);
|
|
46
|
+
this.conceptSystem = new ConceptSystem(brain);
|
|
47
|
+
this.gitBridge = new GitBridge(vfs, brain);
|
|
48
|
+
// Wrap VFS methods to add intelligence WITHOUT slowing them down
|
|
49
|
+
this.wrapMethod(vfs, 'writeFile', async (original, path, data, options) => {
|
|
50
|
+
// Call original first (stays fast)
|
|
51
|
+
const result = await original.call(vfs, path, data, options);
|
|
52
|
+
// Knowledge processing in background (non-blocking)
|
|
53
|
+
setImmediate(async () => {
|
|
54
|
+
try {
|
|
55
|
+
// Record event
|
|
56
|
+
if (this.eventRecorder) {
|
|
57
|
+
await this.eventRecorder.recordEvent({
|
|
58
|
+
type: 'write',
|
|
59
|
+
path,
|
|
60
|
+
content: data,
|
|
61
|
+
size: data.length,
|
|
62
|
+
author: options?.author || 'system'
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Check for semantic versioning
|
|
66
|
+
if (this.semanticVersioning) {
|
|
67
|
+
const existingContent = await vfs.readFile(path).catch(() => null);
|
|
68
|
+
const shouldVersion = existingContent && this.isSemanticChange(existingContent, data);
|
|
69
|
+
if (shouldVersion) {
|
|
70
|
+
await this.semanticVersioning.createVersion(path, data, {
|
|
71
|
+
message: 'Automatic semantic version'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Extract concepts
|
|
76
|
+
if (this.conceptSystem && options?.extractConcepts !== false) {
|
|
77
|
+
await this.conceptSystem.extractAndLinkConcepts(path, data);
|
|
78
|
+
}
|
|
79
|
+
// Extract entities
|
|
80
|
+
if (this.entitySystem && options?.extractEntities !== false) {
|
|
81
|
+
await this.entitySystem.extractEntities(data.toString('utf8'), data);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// Knowledge Layer errors should not affect VFS operations
|
|
86
|
+
console.debug('KnowledgeLayer background processing error:', error);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
});
|
|
91
|
+
this.wrapMethod(vfs, 'unlink', async (original, path) => {
|
|
92
|
+
const result = await original.call(vfs, path);
|
|
93
|
+
// Record deletion event
|
|
94
|
+
setImmediate(async () => {
|
|
95
|
+
if (this.eventRecorder) {
|
|
96
|
+
await this.eventRecorder.recordEvent({
|
|
97
|
+
type: 'delete',
|
|
98
|
+
path,
|
|
99
|
+
author: 'system'
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return result;
|
|
104
|
+
});
|
|
105
|
+
this.wrapMethod(vfs, 'rename', async (original, oldPath, newPath) => {
|
|
106
|
+
const result = await original.call(vfs, oldPath, newPath);
|
|
107
|
+
// Record rename event
|
|
108
|
+
setImmediate(async () => {
|
|
109
|
+
if (this.eventRecorder) {
|
|
110
|
+
await this.eventRecorder.recordEvent({
|
|
111
|
+
type: 'rename',
|
|
112
|
+
path: oldPath,
|
|
113
|
+
metadata: { newPath },
|
|
114
|
+
author: 'system'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return result;
|
|
119
|
+
});
|
|
120
|
+
// Add Knowledge Layer methods to VFS
|
|
121
|
+
this.addKnowledgeMethods(vfs);
|
|
122
|
+
console.log('✨ Knowledge Layer augmentation enabled');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Wrap a VFS method to add Knowledge Layer functionality
|
|
126
|
+
*/
|
|
127
|
+
wrapMethod(vfs, methodName, wrapper) {
|
|
128
|
+
const original = vfs[methodName];
|
|
129
|
+
if (!original)
|
|
130
|
+
return;
|
|
131
|
+
// Store original for cleanup
|
|
132
|
+
this.originalMethods.set(methodName, original);
|
|
133
|
+
// Replace with wrapped version
|
|
134
|
+
vfs[methodName] = async (...args) => {
|
|
135
|
+
return await wrapper(original, ...args);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Add Knowledge Layer methods to VFS
|
|
140
|
+
*/
|
|
141
|
+
addKnowledgeMethods(vfs) {
|
|
142
|
+
// Event history
|
|
143
|
+
vfs.getHistory = async (path, options) => {
|
|
144
|
+
if (!this.eventRecorder)
|
|
145
|
+
throw new Error('Knowledge Layer not initialized');
|
|
146
|
+
return await this.eventRecorder.getHistory(path, options);
|
|
147
|
+
};
|
|
148
|
+
vfs.reconstructAtTime = async (path, timestamp) => {
|
|
149
|
+
if (!this.eventRecorder)
|
|
150
|
+
throw new Error('Knowledge Layer not initialized');
|
|
151
|
+
return await this.eventRecorder.reconstructFileAtTime(path, timestamp);
|
|
152
|
+
};
|
|
153
|
+
// Semantic versioning
|
|
154
|
+
vfs.getVersions = async (path) => {
|
|
155
|
+
if (!this.semanticVersioning)
|
|
156
|
+
throw new Error('Knowledge Layer not initialized');
|
|
157
|
+
return await this.semanticVersioning.getVersions(path);
|
|
158
|
+
};
|
|
159
|
+
vfs.restoreVersion = async (path, versionId) => {
|
|
160
|
+
if (!this.semanticVersioning)
|
|
161
|
+
throw new Error('Knowledge Layer not initialized');
|
|
162
|
+
const version = await this.semanticVersioning.getVersion(path, versionId);
|
|
163
|
+
if (version) {
|
|
164
|
+
await vfs.writeFile(path, version);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
// Entities
|
|
168
|
+
vfs.findEntity = async (query) => {
|
|
169
|
+
if (!this.entitySystem)
|
|
170
|
+
throw new Error('Knowledge Layer not initialized');
|
|
171
|
+
return await this.entitySystem.findEntity(query);
|
|
172
|
+
};
|
|
173
|
+
vfs.getEntityAppearances = async (entityId) => {
|
|
174
|
+
if (!this.entitySystem)
|
|
175
|
+
throw new Error('Knowledge Layer not initialized');
|
|
176
|
+
return await this.entitySystem.getEvolution(entityId);
|
|
177
|
+
};
|
|
178
|
+
// Concepts
|
|
179
|
+
vfs.getConcepts = async (path) => {
|
|
180
|
+
if (!this.conceptSystem)
|
|
181
|
+
throw new Error('Knowledge Layer not initialized');
|
|
182
|
+
const concepts = await this.conceptSystem.findConcepts({ manifestedIn: path });
|
|
183
|
+
return concepts;
|
|
184
|
+
};
|
|
185
|
+
vfs.getConceptGraph = async (options) => {
|
|
186
|
+
if (!this.conceptSystem)
|
|
187
|
+
throw new Error('Knowledge Layer not initialized');
|
|
188
|
+
return await this.conceptSystem.getConceptGraph(options);
|
|
189
|
+
};
|
|
190
|
+
// Git bridge
|
|
191
|
+
vfs.exportToGit = async (vfsPath, gitPath) => {
|
|
192
|
+
if (!this.gitBridge)
|
|
193
|
+
throw new Error('Knowledge Layer not initialized');
|
|
194
|
+
return await this.gitBridge.exportToGit(vfsPath, gitPath);
|
|
195
|
+
};
|
|
196
|
+
vfs.importFromGit = async (gitPath, vfsPath) => {
|
|
197
|
+
if (!this.gitBridge)
|
|
198
|
+
throw new Error('Knowledge Layer not initialized');
|
|
199
|
+
return await this.gitBridge.importFromGit(gitPath, vfsPath);
|
|
200
|
+
};
|
|
201
|
+
// Temporal coupling
|
|
202
|
+
vfs.findTemporalCoupling = async (path, windowMs) => {
|
|
203
|
+
if (!this.eventRecorder)
|
|
204
|
+
throw new Error('Knowledge Layer not initialized');
|
|
205
|
+
return await this.eventRecorder.findTemporalCoupling(path, windowMs);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
isSemanticChange(oldContent, newContent) {
|
|
209
|
+
// Simple heuristic - significant size change or different content
|
|
210
|
+
const oldStr = oldContent.toString('utf8');
|
|
211
|
+
const newStr = newContent.toString('utf8');
|
|
212
|
+
// Check for significant size change (>10%)
|
|
213
|
+
const sizeDiff = Math.abs(oldStr.length - newStr.length) / oldStr.length;
|
|
214
|
+
if (sizeDiff > 0.1)
|
|
215
|
+
return true;
|
|
216
|
+
// Check for structural changes (simplified)
|
|
217
|
+
const oldLines = oldStr.split('\n').filter(l => l.trim());
|
|
218
|
+
const newLines = newStr.split('\n').filter(l => l.trim());
|
|
219
|
+
// Different number of non-empty lines
|
|
220
|
+
return Math.abs(oldLines.length - newLines.length) > 5;
|
|
221
|
+
}
|
|
222
|
+
async cleanup(brain) {
|
|
223
|
+
const vfs = brain.vfs?.();
|
|
224
|
+
if (!vfs)
|
|
225
|
+
return;
|
|
226
|
+
// Restore original methods
|
|
227
|
+
for (const [methodName, original] of this.originalMethods) {
|
|
228
|
+
vfs[methodName] = original;
|
|
229
|
+
}
|
|
230
|
+
// Remove added methods
|
|
231
|
+
delete vfs.getHistory;
|
|
232
|
+
delete vfs.reconstructAtTime;
|
|
233
|
+
delete vfs.getVersions;
|
|
234
|
+
delete vfs.restoreVersion;
|
|
235
|
+
delete vfs.findEntity;
|
|
236
|
+
delete vfs.getEntityAppearances;
|
|
237
|
+
delete vfs.getConcepts;
|
|
238
|
+
delete vfs.getConceptGraph;
|
|
239
|
+
delete vfs.exportToGit;
|
|
240
|
+
delete vfs.importFromGit;
|
|
241
|
+
delete vfs.findTemporalCoupling;
|
|
242
|
+
// Clean up components
|
|
243
|
+
this.eventRecorder = undefined;
|
|
244
|
+
this.semanticVersioning = undefined;
|
|
245
|
+
this.entitySystem = undefined;
|
|
246
|
+
this.conceptSystem = undefined;
|
|
247
|
+
this.gitBridge = undefined;
|
|
248
|
+
console.log('Knowledge Layer augmentation removed');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=KnowledgeAugmentation.js.map
|
package/dist/brainy.d.ts
CHANGED
|
@@ -1566,9 +1566,11 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
1566
1566
|
get counts(): {
|
|
1567
1567
|
entities: () => number;
|
|
1568
1568
|
relationships: () => number;
|
|
1569
|
-
byType: (
|
|
1570
|
-
|
|
1571
|
-
}
|
|
1569
|
+
byType: (typeOrOptions?: string | {
|
|
1570
|
+
excludeVFS?: boolean;
|
|
1571
|
+
}, options?: {
|
|
1572
|
+
excludeVFS?: boolean;
|
|
1573
|
+
}) => Promise<number | Record<string, number>>;
|
|
1572
1574
|
byTypeEnum: (type: NounType) => number;
|
|
1573
1575
|
topTypes: (n?: number) => NounType[];
|
|
1574
1576
|
topVerbTypes: (n?: number) => VerbType[];
|
|
@@ -1579,12 +1581,12 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
1579
1581
|
};
|
|
1580
1582
|
byCriteria: (field: string, value: any) => Promise<number>;
|
|
1581
1583
|
getAllTypeCounts: () => Map<string, number>;
|
|
1582
|
-
getStats: (
|
|
1584
|
+
getStats: (options?: {
|
|
1585
|
+
excludeVFS?: boolean;
|
|
1586
|
+
}) => Promise<{
|
|
1583
1587
|
entities: {
|
|
1584
1588
|
total: number;
|
|
1585
|
-
byType:
|
|
1586
|
-
[k: string]: number;
|
|
1587
|
-
};
|
|
1589
|
+
byType: Record<string, number>;
|
|
1588
1590
|
};
|
|
1589
1591
|
relationships: {
|
|
1590
1592
|
totalRelationships: number;
|
|
@@ -1594,7 +1596,7 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
1594
1596
|
totalNodes: number;
|
|
1595
1597
|
};
|
|
1596
1598
|
density: number;
|
|
1597
|
-
}
|
|
1599
|
+
}>;
|
|
1598
1600
|
};
|
|
1599
1601
|
/**
|
|
1600
1602
|
* Augmentations API - Clean and simple
|
|
@@ -1607,14 +1609,16 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
1607
1609
|
/**
|
|
1608
1610
|
* Get complete statistics - convenience method
|
|
1609
1611
|
* For more granular counting, use brain.counts API
|
|
1612
|
+
* v6.2.1: Added optional excludeVFS using Roaring bitmap intersection
|
|
1613
|
+
* @param options Optional settings - excludeVFS: filter out VFS entities
|
|
1610
1614
|
* @returns Complete statistics including entities, relationships, and density
|
|
1611
1615
|
*/
|
|
1612
|
-
getStats(
|
|
1616
|
+
getStats(options?: {
|
|
1617
|
+
excludeVFS?: boolean;
|
|
1618
|
+
}): Promise<{
|
|
1613
1619
|
entities: {
|
|
1614
1620
|
total: number;
|
|
1615
|
-
byType:
|
|
1616
|
-
[k: string]: number;
|
|
1617
|
-
};
|
|
1621
|
+
byType: Record<string, number>;
|
|
1618
1622
|
};
|
|
1619
1623
|
relationships: {
|
|
1620
1624
|
totalRelationships: number;
|
|
@@ -1624,7 +1628,7 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
1624
1628
|
totalNodes: number;
|
|
1625
1629
|
};
|
|
1626
1630
|
density: number;
|
|
1627
|
-
}
|
|
1631
|
+
}>;
|
|
1628
1632
|
/**
|
|
1629
1633
|
* Parse natural language query using advanced NLP with 220+ patterns
|
|
1630
1634
|
* The embedding model is always available as it's core to Brainy's functionality
|