chore(8.0): final pre-RC1 sweep — API consistency, named errors, orphans, zero-cast codebase

This commit is contained in:
David Snelling 2026-06-11 14:51:00 -07:00
parent 970e08c466
commit 1f7e365a4e
237 changed files with 1951 additions and 49413 deletions

View file

@ -1538,7 +1538,7 @@ Expected behavior after upgrade:
### 🐛 Bug Fixes ### 🐛 Bug Fixes
* **storage**: Fix `clear()` not deleting COW version control data ([#workshop-bug-report](https://github.com/soulcraftlabs/brainy/issues)) * **storage**: Fix `clear()` not deleting COW version control data (consumer-reported)
- Fixed all storage adapters to properly delete `_cow/` directory on clear() - Fixed all storage adapters to properly delete `_cow/` directory on clear()
- Fixed in-memory entity counters not being reset after clear() - Fixed in-memory entity counters not being reset after clear()
- Prevents COW reinitialization after clear() by setting `cowEnabled = false` - Prevents COW reinitialization after clear() by setting `cowEnabled = false`

View file

@ -196,9 +196,10 @@ CSV, Excel, PDF, URLs — auto-detected format, auto-classified entities.
```javascript ```javascript
await brain.import('customers.csv') await brain.import('customers.csv')
await brain.import('sales-data.xlsx', { excelSheets: ['Q1', 'Q2'] }) await brain.import('sales-data.xlsx') // all sheets processed
await brain.import('research-paper.pdf', { pdfExtractTables: true }) await brain.import('research-paper.pdf') // tables extracted automatically
await brain.import('https://api.example.com/data.json') await brain.import('https://api.example.com/data.json')
await brain.import('./handbook.md', { vfsPath: '/imports/handbook' }) // preserve in VFS
``` ```
**[Import Guide](docs/guides/import-anything.md)** **[Import Guide](docs/guides/import-anything.md)**
@ -265,7 +266,7 @@ await brain.relate({
// Filter on the fast path — column-store hit, not metadata fallback: // Filter on the fast path — column-store hit, not metadata fallback:
const employees = await brain.find({ type: NounType.Person, subtype: 'employee' }) const employees = await brain.find({ type: NounType.Person, subtype: 'employee' })
const directReports = await brain.getRelations({ from: ceoId, subtype: 'direct' }) const directReports = await brain.related({ from: ceoId, subtype: 'direct' })
// O(1) counts via the persisted rollups: // O(1) counts via the persisted rollups:
brain.counts.bySubtype(NounType.Person) brain.counts.bySubtype(NounType.Person)

View file

@ -106,6 +106,12 @@ Hard renames, no aliases. Every pair below is verified against the 8.0 source.
| Brainy 7.x | Brainy 8.0 | | Brainy 7.x | Brainy 8.0 |
|---|---| |---|---|
| `brain.delete(id)` | `brain.remove(id)` — matches the transact op vocabulary (`add` / `update` / `remove` / `relate` / `unrelate`) |
| `brain.deleteMany(params)` | `brain.removeMany(params)` |
| `DeleteManyParams` (type) | `RemoveManyParams` |
| `brain.getRelations(paramsOrId)` | `brain.related(paramsOrId)` — the same name a pinned `Db` exposes (`db.related()`) |
| `GetRelationsParams` (type) | `RelatedParams` |
| CLI `delete <id>` | CLI `remove <id>` (JSON output `{ id, removed: true }`, matching `unrelate`) |
| `HnswProvider` (from `@soulcraft/brainy/plugin`) | `VectorIndexProvider` | | `HnswProvider` (from `@soulcraft/brainy/plugin`) | `VectorIndexProvider` |
| `HNSWIndex` (exported class) | `JsHnswVectorIndex` | | `HNSWIndex` (exported class) | `JsHnswVectorIndex` |
| Provider keys `'hnsw'` and `'diskann'` | `'vector'` (the only key Brainy consults for the vector index) | | Provider keys `'hnsw'` and `'diskann'` | `'vector'` (the only key Brainy consults for the vector index) |
@ -129,9 +135,17 @@ find . -name '*.ts' -not -path '*/node_modules/*' -print0 | xargs -0 sed -i \
-e "s/registerProvider('hnsw'/registerProvider('vector'/g" \ -e "s/registerProvider('hnsw'/registerProvider('vector'/g" \
-e "s/registerProvider('diskann'/registerProvider('vector'/g" \ -e "s/registerProvider('diskann'/registerProvider('vector'/g" \
-e "s/getProvider('hnsw'/getProvider('vector'/g" \ -e "s/getProvider('hnsw'/getProvider('vector'/g" \
-e "s/getProvider('diskann'/getProvider('vector'/g" -e "s/getProvider('diskann'/getProvider('vector'/g" \
-e 's/\.getRelations(/.related(/g' \
-e 's/\bGetRelationsParams\b/RelatedParams/g' \
-e 's/\.deleteMany(/.removeMany(/g' \
-e 's/\bDeleteManyParams\b/RemoveManyParams/g'
``` ```
`delete()``remove()` is deliberately **not** in the snippet: `.delete(` is
too common (`Map`/`Set`/storage adapters) for a blind sed. Find your
`brain.delete(...)` call sites and rename them by hand.
The config shape changes need a human (they are not 1:1 textual): The config shape changes need a human (they are not 1:1 textual):
```ts ```ts
@ -192,7 +206,7 @@ this rename; only the API surface moved.
implements the BFS in the JS graph index and routes to a native provider implements the BFS in the JS graph index and routes to a native provider
when one is registered. when one is registered.
- **Every write advances the generation clock; only `transact()` writes - **Every write advances the generation clock; only `transact()` writes
history.** Single-operation writes (`add` / `update` / `delete` / `relate` history.** Single-operation writes (`add` / `update` / `remove` / `relate`
outside `transact()`) bump `brain.generation()` so watermarks and CAS stay outside `transact()`) bump `brain.generation()` so watermarks and CAS stay
sound, but they do not stage historical records — they remain visible sound, but they do not stage historical records — they remain visible
through earlier pins and are not reported by `db.since()`. Writes you want through earlier pins and are not reported by `db.since()`. Writes you want
@ -210,13 +224,20 @@ this rename; only the API surface moved.
no-ops, closing a 7.x trap), system-managed fields drop with a one-shot no-ops, closing a 7.x trap), system-managed fields drop with a one-shot
warning naming the right path; (3) **split at read time** through one warning naming the right path; (3) **split at read time** through one
canonical helper, so `entity.metadata` / `relation.metadata` contain ONLY canonical helper, so `entity.metadata` / `relation.metadata` contain ONLY
custom fields on every read path — `get`, `find`, `getRelations` (several custom fields on every read path — `get`, `find`, `related` (several
paginated/by-source/by-target paths previously echoed the full stored paginated/by-source/by-target paths previously echoed the full stored
record, including the `verb` type key, inside `metadata`), batch reads, record, including the `verb` type key, inside `metadata`), batch reads,
and historical `asOf()` reads. `getRelations()` results now also surface and historical `asOf()` reads. `related()` results now also surface
`confidence`/`updatedAt` top-level, and `updateRelation()` no longer `confidence`/`updatedAt` top-level, and `updateRelation()` no longer
erases a relationship's `service`/`createdBy`. See "Reserved fields" in erases a relationship's `service`/`createdBy`. See "Reserved fields" in
`docs/concepts/consistency-model.md`. `docs/concepts/consistency-model.md`.
- **Named errors for not-found contract failures.** `update()`, `relate()`,
`updateRelation()`, `similar({ to: id })`, `transact()` planning, and
speculative `db.with()` planning now throw `EntityNotFoundError` /
`RelationNotFoundError` (both exported from the package root, both carrying
the missing `id` as a field) instead of a generic `Error`. Message texts are
unchanged, so existing `/not found/` matching keeps working — `instanceof`
is now the supported way to branch.
- **Unchanged from 7.31:** per-entity `_rev`, `update({ ifRev })` - **Unchanged from 7.31:** per-entity `_rev`, `update({ ifRev })`
(`RevisionConflictError`), and `add({ ifAbsent })` work exactly as before, (`RevisionConflictError`), and `add({ ifAbsent })` work exactly as before,
and `ifRev` is also accepted on `transact()` update operations (a conflict and `ifRev` is also accepted on `transact()` update operations (a conflict

View file

@ -28,7 +28,7 @@ path and the native path.
- A **monotonic u64 generation counter** is the store's logical clock. It - A **monotonic u64 generation counter** is the store's logical clock. It
advances once per committed `transact()` batch and once per advances once per committed `transact()` batch and once per
single-operation write (`add`/`update`/`delete`/`relate`/…), so single-operation write (`add`/`update`/`remove`/`relate`/…), so
`brain.generation()` is always a meaningful watermark. It is persisted in `brain.generation()` is always a meaningful watermark. It is persisted in
`_system/generation.json` and never reissued for anything durable. `_system/generation.json` and never reissued for anything durable.
- `brain.now()` **pins** the current generation in O(1) and returns a `Db` - `brain.now()` **pins** the current generation in O(1) and returns a `Db`

View file

@ -316,7 +316,7 @@ const entities = Array.from(results.values())
```typescript ```typescript
const allVerbs = [] const allVerbs = []
for (const sourceId of sourceIds) { for (const sourceId of sourceIds) {
const verbs = await brain.getRelations({ from: sourceId }) const verbs = await brain.related({ from: sourceId })
allVerbs.push(...verbs) allVerbs.push(...verbs)
} }
``` ```

View file

@ -1,435 +0,0 @@
# Creating Augmentations for Brainy
> **Updated** - Includes metadata structure changes and type system improvements
## The BrainyAugmentation Interface
Every augmentation implements this simple yet powerful interface:
```typescript
interface BrainyAugmentation {
// Identification
name: string // Unique name for your augmentation
// Execution control
timing: 'before' | 'after' | 'around' | 'replace' // When to execute
operations: string[] // Which operations to intercept
priority: number // Execution order (higher = first)
// Lifecycle methods
initialize(context: AugmentationContext): Promise<void>
execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T>
shutdown?(): Promise<void> // Optional cleanup
}
```
## Breaking Changes for Augmentation Developers
### 1. Metadata Structure Separation
Brainy introduces strict metadata/vector separation for billion-scale performance:
```typescript
// ✅ Metadata has required type field
interface NounMetadata {
noun: NounType // Required! Must be a valid noun type
[key: string]: any // Your custom metadata
}
interface VerbMetadata {
verb: VerbType // Required! Must be a valid verb type
sourceId: string
targetId: string
[key: string]: any
}
```
### 2. Storage Adapter Return Types
Storage adapters now return different types at different boundaries:
```typescript
// Internal methods: Pure structures (no metadata)
abstract _getNoun(id: string): Promise<HNSWNoun | null>
// Public API: WithMetadata structures
abstract getNoun(id: string): Promise<HNSWNounWithMetadata | null>
```
### 3. Verb Property Renamed
The verb relationship field changed from `type` to `verb`:
```typescript
// ❌ v3.x
verb.type === 'relatedTo'
// ✅ Current
verb.verb === 'relatedTo'
```
## Creating a Storage Augmentation
Storage augmentations are special - they provide the storage backend for Brainy.
### Important: Storage Requirements
Your storage adapter MUST:
1. **Wrap metadata** with required `noun`/`verb` fields
2. **Return pure structures** from internal `_methods`
3. **Return WithMetadata types** from public methods
```typescript
import { StorageAugmentation } from 'brainy/augmentations'
import { BaseStorageAdapter, HNSWNoun, HNSWNounWithMetadata, NounMetadata } from 'brainy'
export class MyCustomStorage extends BaseStorageAdapter {
// Internal method: Returns pure structure
async _getNoun(id: string): Promise<HNSWNoun | null> {
const data = await this.fetchFromDatabase(id)
return data ? {
id: data.id,
vector: data.vector,
nounType: data.type
} : null
}
// Public method: Returns WithMetadata structure
async getNoun(id: string): Promise<HNSWNounWithMetadata | null> {
const noun = await this._getNoun(id)
if (!noun) return null
// Fetch metadata separately
const metadata = await this.getNounMetadata(id)
return {
...noun,
metadata: metadata || { noun: noun.nounType || 'thing' }
}
}
// CRITICAL: Always save with proper metadata structure
async saveNoun(noun: HNSWNoun, metadata?: NounMetadata): Promise<void> {
// Validate metadata has required 'noun' field
if (!metadata?.noun) {
throw new Error('NounMetadata requires "noun" field')
}
await this.database.save({
id: noun.id,
vector: noun.vector,
nounType: noun.nounType,
metadata: metadata // Stored separately
})
}
}
export class MyStorageAugmentation extends StorageAugmentation {
private config: MyStorageConfig
constructor(config: MyStorageConfig) {
super()
this.name = 'my-custom-storage'
this.config = config
}
// Called during storage resolution phase
async provideStorage(): Promise<StorageAdapter> {
const storage = new MyCustomStorage(this.config)
this.storageAdapter = storage
return storage
}
// Called during augmentation initialization
protected async onInitialize(): Promise<void> {
await this.storageAdapter!.init()
this.log(`Custom storage initialized`)
}
}
```
### Using Your Storage Augmentation
```typescript
// Register before brain.init()
const brain = new Brainy()
brain.augmentations.register(new MyStorageAugmentation({
connectionString: 'redis://localhost:6379'
}))
await brain.init() // Will use your storage!
```
## Creating a Feature Augmentation
Here's a complete example of a caching augmentation:
```typescript
import { BaseAugmentation, BrainyAugmentation } from 'brainy/augmentations'
export class CachingAugmentation extends BaseAugmentation {
private cache = new Map<string, any>()
constructor() {
super()
this.name = 'smart-cache'
this.timing = 'around' // Wrap operations
this.operations = ['search'] // Only cache searches
this.priority = 50 // Mid-priority
}
async execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T> {
if (operation === 'search') {
// Check cache
const cacheKey = JSON.stringify(params)
if (this.cache.has(cacheKey)) {
this.log('Cache hit!')
return this.cache.get(cacheKey)
}
// Execute and cache
const result = await next()
this.cache.set(cacheKey, result)
return result
}
// Pass through other operations
return next()
}
protected async onInitialize(): Promise<void> {
this.log('Cache initialized')
}
async shutdown(): Promise<void> {
this.cache.clear()
await super.shutdown()
}
}
```
## The Four Timing Modes
### 1. `before` - Pre-processing
```typescript
timing = 'before'
async execute(op, params, next) {
// Validate/transform input
const validated = await validate(params)
return next(validated) // Pass modified params
}
```
### 2. `after` - Post-processing
```typescript
timing = 'after'
async execute(op, params, next) {
const result = await next()
// Log, analyze, or modify result
console.log(`Operation ${op} returned:`, result)
return result
}
```
### 3. `around` - Wrapping (middleware)
```typescript
timing = 'around'
async execute(op, params, next) {
console.log('Starting', op)
try {
const result = await next()
console.log('Success', op)
return result
} catch (error) {
console.log('Failed', op, error)
throw error
}
}
```
### 4. `replace` - Complete replacement
```typescript
timing = 'replace'
async execute(op, params, next) {
// Don't call next() - replace entirely!
return myCustomImplementation(params)
}
```
## Operations You Can Intercept
Common operations in Brainy:
- `'storage'` - Storage resolution (special)
- `'add'` - Adding data
- `'search'`, `'similar'` - Searching
- `'update'`, `'delete'` - Modifications
- `'saveNoun'`, `'saveVerb'` - Storage operations
- `'all'` - Intercept everything
## Context Available to Augmentations
```typescript
interface AugmentationContext {
brain: Brainy // The brain instance
storage: StorageAdapter // Storage backend
config: BrainyConfig // Configuration
log: (message: string, level?: 'info' | 'warn' | 'error') => void
}
```
## Real-World Examples
### 1. Redis Storage Augmentation
```typescript
export class RedisStorageAugmentation extends StorageAugmentation {
async provideStorage(): Promise<StorageAdapter> {
return new RedisAdapter({
host: 'localhost',
port: 6379,
// Implement full StorageAdapter interface
})
}
}
```
### 2. Audit Trail Augmentation
```typescript
export class AuditAugmentation extends BaseAugmentation {
timing = 'after'
operations = ['add', 'update', 'delete']
async execute(op, params, next) {
const result = await next()
// Log to audit trail
await this.logAudit({
operation: op,
params,
result,
timestamp: new Date(),
user: this.context.config.currentUser
})
return result
}
}
```
### 3. Rate Limiting Augmentation
```typescript
export class RateLimitAugmentation extends BaseAugmentation {
timing = 'before'
operations = ['search']
private limiter = new RateLimiter({ rps: 100 })
async execute(op, params, next) {
await this.limiter.acquire() // Wait if rate limited
return next()
}
}
```
## Publishing to Brain Cloud Marketplace
Future capability for premium augmentations:
```typescript
// package.json
{
"name": "@brain-cloud/redis-storage",
"brainy": {
"type": "augmentation",
"category": "storage",
"premium": true
}
}
// Users can install via:
// brainy augment install redis-storage
```
## Best Practices
### General Practices
1. **Use BaseAugmentation** - Provides common functionality
2. **Set appropriate priority** - Storage (100), System (80-99), Features (10-50)
3. **Be selective with operations** - Don't use 'all' unless necessary
4. **Handle errors gracefully** - Don't break the chain
5. **Clean up in shutdown()** - Release resources
6. **Log appropriately** - Use context.log() for consistent output
7. **Document your augmentation** - Include examples
### Specific Best Practices
8. **Always include `noun` field** when creating/modifying NounMetadata:
```typescript
const metadata: NounMetadata = {
noun: 'thing', // REQUIRED!
yourField: 'value'
}
```
9. **Use `verb` property** not `type` when working with relationships:
```typescript
// ✅ Correct
if (verb.verb === 'relatedTo') { ... }
// ❌ Wrong (v3.x pattern)
if (verb.type === 'relatedTo') { ... }
```
10. **Access metadata correctly** from storage:
```typescript
// ✅ Correct - metadata is already structured
const nounType = noun.metadata.noun
// ⚠️ Fallback pattern for robustness
const nounType = noun.metadata?.noun || 'thing'
```
11. **Respect the two-file storage pattern** - Don't mix vector and metadata operations:
```typescript
// ✅ Good - Separate concerns
await storage.saveNoun(noun)
await storage.saveMetadata(noun.id, metadata)
// ❌ Bad - Mixing concerns
await storage.saveNounWithEverything(combinedData)
```
## Testing Your Augmentation
```typescript
import { Brainy } from 'brainy'
import { MyAugmentation } from './my-augmentation'
describe('MyAugmentation', () => {
let brain: Brainy
beforeEach(async () => {
brain = new Brainy()
brain.augmentations.register(new MyAugmentation())
await brain.init()
})
afterEach(async () => {
await brain.destroy()
})
it('should enhance searches', async () => {
// Test your augmentation's effect
const results = await brain.search('test')
expect(results).toHaveProperty('enhanced', true)
})
})
```
## Summary
Augmentations are Brainy's extension system. They can:
- Replace storage backends
- Add caching layers
- Implement audit trails
- Add rate limiting
- Sync with external systems
- Transform data
- And much more!
The unified BrainyAugmentation interface makes it easy to create powerful extensions while maintaining consistency across the entire system.

View file

@ -238,7 +238,7 @@ await brain.relate({
Fast-path filter on the verb side: Fast-path filter on the verb side:
```typescript ```typescript
const direct = await brain.getRelations({ const direct = await brain.related({
from: ceoId, from: ceoId,
type: VerbType.ReportsTo, type: VerbType.ReportsTo,
subtype: 'direct' subtype: 'direct'

View file

@ -193,21 +193,21 @@ console.log('✅ Created relationships')
console.log('\n🔍 Querying relationships...') console.log('\n🔍 Querying relationships...')
// Who works for TechCorp? // Who works for TechCorp?
const techcorpEmployees = await brain.getRelations({ const techcorpEmployees = await brain.related({
to: techcorpId, to: techcorpId,
type: VerbType.WorksWith type: VerbType.WorksWith
}) })
console.log(`TechCorp has ${techcorpEmployees.length} employees`) console.log(`TechCorp has ${techcorpEmployees.length} employees`)
// Who works on the AI project? // Who works on the AI project?
const projectContributors = await brain.getRelations({ const projectContributors = await brain.related({
to: projectId, to: projectId,
type: [VerbType.WorksOn, VerbType.Manages] type: [VerbType.WorksOn, VerbType.Manages]
}) })
console.log(`AI Project has ${projectContributors.length} contributors`) console.log(`AI Project has ${projectContributors.length} contributors`)
// Who does John collaborate with? // Who does John collaborate with?
const johnsCollaborators = await brain.getRelations({ const johnsCollaborators = await brain.related({
from: johnId, from: johnId,
type: VerbType.CollaboratesWith type: VerbType.CollaboratesWith
}) })
@ -294,7 +294,7 @@ await brain.update({
1. Create an organizational hierarchy (CEO → Managers → Engineers) 1. Create an organizational hierarchy (CEO → Managers → Engineers)
2. Build a project dependency graph 2. Build a project dependency graph
3. Model a social network with CollaboratesWith relationships 3. Model a social network with CollaboratesWith relationships
4. Query "Who reports to Alice?" using getRelations() 4. Query "Who reports to Alice?" using related()
5. Batch update all projects to add a "year: 2024" field 5. Batch update all projects to add a "year: 2024" field
### Next Steps ### Next Steps
@ -676,7 +676,7 @@ if (readmeEntity.length > 0) {
} }
// Query relationships // Query relationships
const conceptDocs = await brain.getRelations({ const conceptDocs = await brain.related({
from: conceptId, from: conceptId,
type: VerbType.DocumentedBy type: VerbType.DocumentedBy
}) })
@ -798,7 +798,7 @@ await brain.relate({
}) })
// Query across boundaries // Query across boundaries
const conceptDocs = await brain.getRelations({ const conceptDocs = await brain.related({
from: conceptId, from: conceptId,
type: VerbType.DocumentedBy type: VerbType.DocumentedBy
}) })

View file

@ -1228,7 +1228,7 @@ where: { age: { gt: 18 } }
**No connected entities found**: **No connected entities found**:
```typescript ```typescript
// Verify relationship exists // Verify relationship exists
const relations = await brain.getRelations({ const relations = await brain.related({
from: 'entity-a', from: 'entity-a',
to: 'entity-b' to: 'entity-b'
}) })

View file

@ -48,7 +48,7 @@ await storage.setLifecyclePolicy({
```typescript ```typescript
// v3: Delete one at a time (slow, expensive) // v3: Delete one at a time (slow, expensive)
for (const id of idsToDelete) { for (const id of idsToDelete) {
await brain.delete(id) // 1000 API calls for 1000 entities await brain.remove(id) // 1000 API calls for 1000 entities
} }
// v4.0.0: Batch delete (fast, cheap) // v4.0.0: Batch delete (fast, cheap)

View file

@ -216,18 +216,18 @@ See the **[Subtypes & Facets guide](./guides/subtypes-and-facets.md)** for the f
### Filter relationships by subtype (7.30+) ### Filter relationships by subtype (7.30+)
Verbs are first-class peers — `getRelations()` and graph traversal both honor subtype filters on the fast path: Verbs are first-class peers — `related()` and graph traversal both honor subtype filters on the fast path:
```typescript ```typescript
// Filter relationships by VerbType subtype // Filter relationships by VerbType subtype
const direct = await brain.getRelations({ const direct = await brain.related({
from: ceoId, from: ceoId,
type: VerbType.ReportsTo, type: VerbType.ReportsTo,
subtype: 'direct' subtype: 'direct'
}) })
// Set membership on verb subtype // Set membership on verb subtype
const all = await brain.getRelations({ const all = await brain.related({
from: ceoId, from: ceoId,
type: VerbType.ReportsTo, type: VerbType.ReportsTo,
subtype: ['direct', 'dotted-line'] subtype: ['direct', 'dotted-line']

View file

@ -89,14 +89,11 @@ See [vfs/](./vfs/) for the complete VFS documentation set.
--- ---
## Plugins & Augmentations ## Plugins
| Document | Description | | Document | Description |
|----------|-------------| |----------|-------------|
| [Plugins](./PLUGINS.md) | Plugin system overview | | [Plugins](./PLUGINS.md) | Plugin system overview — providers, `plugins` config, `brain.use()` |
| [Creating Augmentations](./CREATING-AUGMENTATIONS.md) | Build custom plugins |
| [Augmentations Reference](./augmentations/COMPLETE-REFERENCE.md) | Full augmentation API |
| [Augmentations Developer Guide](./augmentations/DEVELOPER-GUIDE.md) | Plugin development guide |
--- ---

View file

@ -185,12 +185,12 @@ await brain.update({
--- ---
### `delete(id)` → `Promise<void>` ### `remove(id)` → `Promise<void>`
Delete a single entity. Remove a single entity (and every relationship where it is source or target).
```typescript ```typescript
await brain.delete(id) await brain.remove(id)
``` ```
**Parameters:** **Parameters:**
@ -444,7 +444,7 @@ brain.defineAggregate({
name: 'monthly_spending', name: 'monthly_spending',
source: { source: {
type: NounType.Event, type: NounType.Event,
where: { domain: 'financial', subtype: 'transaction' } where: { domain: 'financial' } // matches custom metadata fields
}, },
groupBy: [ groupBy: [
'category', 'category',
@ -468,10 +468,10 @@ brain.defineAggregate({
|-------|------|-------------| |-------|------|-------------|
| `name` | `string` | Unique identifier for this aggregate | | `name` | `string` | Unique identifier for this aggregate |
| `source.type` | `NounType \| NounType[]` | Entity types that feed into this aggregate | | `source.type` | `NounType \| NounType[]` | Entity types that feed into this aggregate |
| `source.where` | `Record<string, unknown>` | Metadata filter (same syntax as `find({ where })`) | | `source.where` | `Record<string, unknown>` | Filter on custom **metadata** fields (matched against the entity's `metadata` bag) |
| `source.service` | `string` | Multi-tenancy filter | | `source.service` | `string` | Multi-tenancy filter |
| `groupBy` | `GroupByDimension[]` | Dimensions to group by — plain field names or `{ field, window }` for time bucketing | | `groupBy` | `GroupByDimension[]` | Dimensions to group by — plain field names, `{ field, window }` for time bucketing, or `{ field, unnest: true }` for array fields (one contribution per element) |
| `metrics` | `Record<string, AggregateMetricDef>` | Named metrics with `op` (`sum`, `count`, `avg`, `min`, `max`, `stddev`, `variance`) and optional `field` | | `metrics` | `Record<string, AggregateMetricDef>` | Named metrics with `op` (`sum`, `count`, `avg`, `min`, `max`, `stddev`, `variance`, `percentile`, `distinctCount`) and optional `field`. `percentile` additionally requires `p` in `[0, 1]`. |
| `materialize` | `boolean \| object` | Write results as `NounType.Measurement` entities (auto-visible in OData/Sheets/SSE) | | `materialize` | `boolean \| object` | Write results as `NounType.Measurement` entities (auto-visible in OData/Sheets/SSE) |
**Time window granularities:** `'hour'`, `'day'`, `'week'`, `'month'`, `'quarter'`, `'year'`, or `{ seconds: number }` for custom intervals. **Time window granularities:** `'hour'`, `'day'`, `'week'`, `'month'`, `'quarter'`, `'year'`, or `{ seconds: number }` for custom intervals.
@ -539,6 +539,20 @@ const recentFood = await brain.find({
} }
``` ```
### `queryAggregate(name, params?)``Promise<AggregateResult[]>`
The first-class analytics path — returns plain group rows (`{ groupKey, metrics, count }`) instead of `find()`-style `Result` wrappers. Supports `where` (group-key filter), `having` (SQL-HAVING metric filter), `orderBy`, `order`, `limit`, `offset`:
```typescript
const rows = await brain.queryAggregate('monthly_spending', {
having: { total: { greaterThan: 100 } }, // filter by computed metrics
orderBy: 'total',
order: 'desc',
limit: 10
})
// [{ groupKey: { category: 'food', date: '2024-01' }, metrics: { total: 342.5, count: 28 }, count: 28 }, ...]
```
### How It Works ### How It Works
Aggregation hooks run **outside transactions** on every write operation: Aggregation hooks run **outside transactions** on every write operation:
@ -557,16 +571,16 @@ Aggregation hooks run **outside transactions** on every write operation:
### Financial Data Modeling ### Financial Data Modeling
Brainy supports financial analytics through **metadata conventions** on existing NounTypes — no custom types needed: Brainy supports financial analytics through **subtypes and metadata conventions** on existing NounTypes — no custom types needed:
```typescript ```typescript
// Transaction = NounType.Event + financial metadata // Transaction = NounType.Event + 'transaction' subtype + financial metadata
await brain.add({ await brain.add({
data: 'Coffee at Blue Bottle', data: 'Coffee at Blue Bottle',
type: NounType.Event, type: NounType.Event,
subtype: 'transaction', // top-level standard field (reserved — never in metadata)
metadata: { metadata: {
domain: 'financial', domain: 'financial',
subtype: 'transaction',
amount: 5.50, amount: 5.50,
currency: 'USD', currency: 'USD',
category: 'food', category: 'food',
@ -575,26 +589,26 @@ await brain.add({
} }
}) })
// Account = NounType.Collection + financial metadata // Account = NounType.Collection + 'account' subtype + financial metadata
await brain.add({ await brain.add({
data: 'Checking Account', data: 'Checking Account',
type: NounType.Collection, type: NounType.Collection,
subtype: 'account',
metadata: { metadata: {
domain: 'financial', domain: 'financial',
subtype: 'account',
accountType: 'checking', accountType: 'checking',
currency: 'USD', currency: 'USD',
institution: 'Chase' institution: 'Chase'
} }
}) })
// Invoice = NounType.Document + financial metadata // Invoice = NounType.Document + 'invoice' subtype + financial metadata
await brain.add({ await brain.add({
data: 'Invoice #1234 from Acme Corp', data: 'Invoice #1234 from Acme Corp',
type: NounType.Document, type: NounType.Document,
subtype: 'invoice',
metadata: { metadata: {
domain: 'financial', domain: 'financial',
subtype: 'invoice',
amount: 15000, amount: 15000,
currency: 'USD', currency: 'USD',
status: 'pending', status: 'pending',
@ -672,32 +686,33 @@ await brain.updateRelation({ id: relId, type: VerbType.WorksWith })
--- ---
### `getRelations(params)` → `Promise<Relation[]>` ### `related(params)` → `Promise<Relation[]>`
Get relationships for an entity. Get relationships for an entity. Same name and surface as `db.related()` on a
pinned `Db` view.
```typescript ```typescript
// Get all relationships FROM an entity // Get all relationships FROM an entity
const outgoing = await brain.getRelations({ from: entityId }) const outgoing = await brain.related({ from: entityId })
// Get all relationships TO an entity // Get all relationships TO an entity
const incoming = await brain.getRelations({ to: entityId }) const incoming = await brain.related({ to: entityId })
// Filter by type // Filter by type
const related = await brain.getRelations({ const contains = await brain.related({
from: entityId, from: entityId,
type: VerbType.Contains type: VerbType.Contains
}) })
// Filter by subtype (fast path, column-store hit) // Filter by subtype (fast path, column-store hit)
const direct = await brain.getRelations({ const direct = await brain.related({
from: entityId, from: entityId,
type: VerbType.ReportsTo, type: VerbType.ReportsTo,
subtype: 'direct' subtype: 'direct'
}) })
// Set membership on subtype // Set membership on subtype
const all = await brain.getRelations({ const all = await brain.related({
from: entityId, from: entityId,
type: VerbType.ReportsTo, type: VerbType.ReportsTo,
subtype: ['direct', 'dotted-line'] subtype: ['direct', 'dotted-line']
@ -739,12 +754,12 @@ console.log(result.failed) // Array of errors
--- ---
### `deleteMany(params)` → `Promise<BatchResult<string>>` ### `removeMany(params)` → `Promise<BatchResult<string>>`
Delete multiple entities. Remove multiple entities.
```typescript ```typescript
const result = await brain.deleteMany({ const result = await brain.removeMany({
ids: [id1, id2, id3] ids: [id1, id2, id3]
}) })
``` ```
@ -757,7 +772,7 @@ Update multiple entities.
```typescript ```typescript
const result = await brain.updateMany({ const result = await brain.updateMany({
updates: [ items: [
{ id: id1, metadata: { updated: true } }, { id: id1, metadata: { updated: true } },
{ id: id2, data: 'New content' } { id: id2, data: 'New content' }
] ]
@ -772,7 +787,7 @@ Create multiple relationships.
```typescript ```typescript
const ids = await brain.relateMany({ const ids = await brain.relateMany({
relations: [ items: [
{ from: id1, to: id2, type: VerbType.RelatedTo }, { from: id1, to: id2, type: VerbType.RelatedTo },
{ from: id1, to: id3, type: VerbType.Contains } { from: id1, to: id3, type: VerbType.Contains }
] ]
@ -1213,39 +1228,16 @@ const todos = await brain.vfs.getTodos('/src/App.tsx')
--- ---
#### `vfs.getAllTodos(path?)` → `Promise<Todo[]>` #### `vfs.searchEntities(query)` → `Promise<Array<{ id, path, type, metadata }>>`
Get all TODOs from directory tree. Search for semantic entities tracked by the VFS, filtered by type, name, or metadata.
```typescript ```typescript
const allTodos = await brain.vfs.getAllTodos('/src') const people = await brain.vfs.searchEntities({
``` type: 'person', // entity type filter
name: 'Ada', // semantic name search
--- where: { role: 'author' }, // metadata filters
limit: 50
### Project Analysis
#### `vfs.getProjectStats(path?)``Promise<Stats>`
Get project statistics.
```typescript
const stats = await brain.vfs.getProjectStats('/projects/my-app')
console.log(stats.fileCount)
console.log(stats.totalSize)
console.log(stats.fileTypes) // Breakdown by extension
```
---
#### `vfs.searchEntities(query)``Promise<VFSEntity[]>`
Search for VFS entities by metadata.
```typescript
const tsxFiles = await brain.vfs.searchEntities({
type: 'file',
extension: '.tsx'
}) })
``` ```
@ -1280,48 +1272,52 @@ console.log(result.explanation)
--- ---
### `neural().clusters(input?, options?)``Promise<Cluster[]>` ### `neural().clusters(input?, options?)``Promise<SemanticCluster[]>`
Automatic clustering. Automatic clustering. Accepts a text query, an array of texts, or a `ClusteringOptions` object.
```typescript ```typescript
const clusters = await brain.neural().clusters({ const clusters = await brain.neural().clusters({
algorithm: 'kmeans', algorithm: 'kmeans', // 'auto' | 'hierarchical' | 'kmeans' | 'dbscan' | ...
k: 5, maxClusters: 5,
minSize: 3 minClusterSize: 3
}) })
clusters.forEach(cluster => { clusters.forEach(cluster => {
console.log(cluster.label) console.log(cluster.label) // auto-generated label (when available)
console.log(cluster.items) console.log(cluster.members) // entity ids in the cluster
console.log(cluster.centroid) console.log(cluster.centroid) // centroid vector
}) })
``` ```
--- ---
### `neural().neighbors(id, options?)``Promise<Neighbor[]>` ### `neural().neighbors(id, options?)``Promise<NeighborsResult>`
Find k-nearest neighbors. Find nearest neighbors.
```typescript ```typescript
const neighbors = await brain.neural().neighbors(entityId, { const result = await brain.neural().neighbors(entityId, {
k: 10, limit: 10,
threshold: 0.7 minSimilarity: 0.7
}) })
``` ```
**Options:** `limit?`, `radius?`, `minSimilarity?`, `includeMetadata?`, `sortBy?: 'similarity' | 'importance' | 'recency'`
--- ---
### `neural().outliers(threshold?)` → `Promise<string[]>` ### `neural().outliers(options?)` → `Promise<Outlier[]>`
Detect outlier entities. Detect outlier entities.
```typescript ```typescript
const outliers = await brain.neural().outliers(0.3) const outliers = await brain.neural().outliers({ threshold: 0.3 })
// Returns entity IDs that are outliers // Each outlier carries the entity id plus outlier scoring details
``` ```
**Options:** `threshold?`, `method?: 'isolation' | 'statistical' | 'cluster-based'`, `minNeighbors?`, `includeReasons?`
--- ---
### `neural().visualize(options?)``Promise<VizData>` ### `neural().visualize(options?)``Promise<VizData>`
@ -1342,27 +1338,28 @@ const vizData = await brain.neural().visualize({
### Performance Methods ### Performance Methods
#### `neural().clusterFast(options)` → `Promise<Cluster[]>` #### `neural().clusterFast(options?)` → `Promise<SemanticCluster[]>`
Fast clustering for large datasets. Fast clustering for large datasets.
```typescript ```typescript
const clusters = await brain.neural().clusterFast({ const clusters = await brain.neural().clusterFast({
k: 10, maxClusters: 10
maxIterations: 50
}) })
``` ```
**Options:** `level?` (hierarchy level), `maxClusters?`
--- ---
#### `neural().clusterLarge(options)` → `Promise<Cluster[]>` #### `neural().clusterLarge(options?)` → `Promise<SemanticCluster[]>`
Streaming clustering for very large datasets. Sampling-based clustering for very large datasets.
```typescript ```typescript
const clusters = await brain.neural().clusterLarge({ const clusters = await brain.neural().clusterLarge({
k: 20, sampleSize: 1000,
batchSize: 1000 strategy: 'diverse' // 'random' | 'diverse' | 'recent'
}) })
``` ```
@ -1381,17 +1378,15 @@ await brain.import('data.csv', {
createEntities: true createEntities: true
}) })
// Excel import // Excel import (all sheets processed automatically)
await brain.import('sales.xlsx', { await brain.import('sales.xlsx', {
format: 'excel', format: 'excel',
sheets: ['Q1', 'Q2'] vfsPath: '/imports/sales', // optional: mirror into the VFS
groupBy: 'sheet'
}) })
// PDF import // PDF import (tables extracted automatically)
await brain.import('research.pdf', { await brain.import('research.pdf', { format: 'pdf' })
format: 'pdf',
extractTables: true
})
// URL import // URL import
await brain.import('https://api.example.com/data.json') await brain.import('https://api.example.com/data.json')
@ -1400,10 +1395,17 @@ await brain.import('https://api.example.com/data.json')
**Parameters:** **Parameters:**
- `source`: `string | Buffer | object` - File path, URL, buffer, or object - `source`: `string | Buffer | object` - File path, URL, buffer, or object
- `options?`: Import configuration - `options?`: Import configuration
- `format?`: `'csv' | 'excel' | 'pdf' | 'json'` - Auto-detected if omitted - `format?`: `'excel' | 'pdf' | 'csv' | 'json' | 'markdown' | 'yaml' | 'docx' | 'image'` - Auto-detected if omitted
- `vfsPath?`: `string` - Mirror imported content into the VFS at this path
- `groupBy?`: `'type' | 'sheet' | 'flat' | 'custom'` - VFS grouping strategy
- `createEntities?`: `boolean` - Create entities from rows - `createEntities?`: `boolean` - Create entities from rows
- `sheets?`: `string[]` - Excel sheets to import - `createRelationships?`: `boolean` - Create relationships between extracted entities
- `extractTables?`: `boolean` - Extract tables from PDF - `preserveSource?`: `boolean` - Save the original file in the VFS
- `enableNeuralExtraction?`: `boolean` - Extract entity names via AI
- `enableRelationshipInference?`: `boolean` - Infer relationships via AI
- `enableConceptExtraction?`: `boolean` - Extract entity types via AI
- `confidenceThreshold?`: `number` - Minimum confidence for extracted entities
- `onProgress?`: `(progress) => void` - Progress callback (stage, counts, throughput, ETA)
**Returns:** `Promise<ImportResult>` - Import statistics **Returns:** `Promise<ImportResult>` - Import statistics
@ -1414,9 +1416,6 @@ await brain.import('https://api.example.com/data.json')
### Export & Snapshots ### Export & Snapshots
```typescript ```typescript
// Export to file
await brain.export('/path/to/backup.brainy')
// Instant hard-link snapshot via the Db API // Instant hard-link snapshot via the Db API
const pin = brain.now() const pin = brain.now()
await pin.persist('/backups/2026-06-11') await pin.persist('/backups/2026-06-11')
@ -1453,12 +1452,11 @@ const brain = new Brainy({
// Model: all-MiniLM-L6-v2 (384 dimensions) // Model: all-MiniLM-L6-v2 (384 dimensions)
// Device: CPU via WASM (works everywhere) // Device: CPU via WASM (works everywhere)
// Cache configuration // Cache configuration — `true`/`false`, or an options object
cache: { cache: {
enabled: true, maxSize: 10000,
maxSize: 10000, ttl: 3600000 // 1 hour in ms
ttl: 3600000 // 1 hour in ms }
}
}) })
await brain.init() // Required! VFS auto-initialized await brain.init() // Required! VFS auto-initialized
@ -1672,7 +1670,7 @@ When strict mode is on:
- Brainy's own VFS infrastructure writes bypass via the `metadata.isVFSEntity: true` marker. - Brainy's own VFS infrastructure writes bypass via the `metadata.isVFSEntity: true` marker.
- Per-type registrations always apply regardless of the brain-wide flag. - Per-type registrations always apply regardless of the brain-wide flag.
Becomes the default in 8.0.0. The default since 8.0.0 — pass `requireSubtype: false` to opt out while migrating pre-8.0 data.
#### `trackField(name, options?)``void` #### `trackField(name, options?)``void`
@ -1718,6 +1716,27 @@ await brain.migrateField({
Returns `{ scanned: number, migrated: number, skipped: number, errors: Array<{id, error}> }`. Returns `{ scanned: number, migrated: number, skipped: number, errors: Array<{id, error}> }`.
#### `fillSubtypes(rules, options?)``Promise<FillSubtypesResult>` (8.0+)
Back-fill missing `subtype` values across entities AND relationships in one streaming pass — the migration companion to `audit()`. Keys are `NounType`/`VerbType` values; each rule is a literal subtype string or a function deriving one from the entry (return `undefined` to decline). Idempotent: entries that already carry a subtype are never touched, so a crashed run is resumed safely by re-running.
```typescript
const report = await brain.fillSubtypes({
[NounType.Person]: (e) => e.metadata?.kind ?? 'unspecified', // derived
[NounType.Document]: 'general', // literal default
[VerbType.RelatedTo]: 'unspecified' // relationship rule
})
// → { scanned, filled, skipped, errors, byType }
```
**Parameters:**
- `rules`: `FillSubtypeRules` - Map of NounType/VerbType → literal subtype or `(entry) => string | undefined`
- `options.includeVFS?`: `boolean` - Also fill VFS infrastructure entries (default `false`)
- `options.batchSize?`: `number` - Pagination batch size (default `200`)
- `options.onProgress?`: `(progress: { scanned, filled, skipped }) => void` - Per-batch callback
Returns `{ scanned, filled, skipped, errors, byType }`. After a clean run, `skipped` equals the remaining `audit().total`. See the [migration recipe](../guides/subtypes-and-facets.md).
--- ---
### `embed(data)``Promise<number[]>` ### `embed(data)``Promise<number[]>`
@ -1812,7 +1831,7 @@ for (const group of duplicates) {
// Find person duplicates with higher threshold // Find person duplicates with higher threshold
const personDupes = await brain.findDuplicates({ const personDupes = await brain.findDuplicates({
type: NounType.PERSON, type: NounType.Person,
threshold: 0.9, threshold: 0.9,
limit: 50 limit: 50
}) })
@ -1885,15 +1904,19 @@ const docClusters = await brain.cluster({
--- ---
### `getStats()` → `Statistics` ### `getStats(options?)` → `Promise<Statistics>`
Get comprehensive statistics. Get complete entity/relationship statistics (convenience wrapper over `brain.counts`).
```typescript ```typescript
const stats = brain.getStats() const stats = await brain.getStats()
console.log(stats.entityCount) console.log(stats.entities.total) // total entity count
console.log(stats.relationshipCount) console.log(stats.entities.byType) // counts per NounType
console.log(stats.cacheHitRate) console.log(stats.relationships) // relationship stats
console.log(stats.density) // relationships per entity
// Exclude VFS infrastructure entities from the counts
const semanticOnly = await brain.getStats({ excludeVFS: true })
``` ```
--- ---
@ -1914,7 +1937,7 @@ VFS is auto-initialized during `brain.init()` - no separate `vfs.init()` needed!
### Shutdown ### Shutdown
```typescript ```typescript
await brain.shutdown() // Graceful shutdown, flush caches await brain.close() // Graceful shutdown — flushes pending writes and releases the writer lock
``` ```
--- ---
@ -1940,8 +1963,8 @@ await brain.update({
metadata: { updated: true } metadata: { updated: true }
}) })
// Delete // Remove
await brain.delete(id) await brain.remove(id)
``` ```
--- ---
@ -2001,7 +2024,7 @@ const results = await brain.find({
// Speculate: what would this change look like? (nothing touches disk) // Speculate: what would this change look like? (nothing touches disk)
const base = brain.now() const base = brain.now()
const whatIf = await base.with([ const whatIf = await base.with([
{ op: 'add', type: NounType.Document, subtype: 'note', data: 'New feature' } { op: 'add', type: NounType.Document, subtype: 'note', data: 'New feature', metadata: { draft: true } }
]) ])
await whatIf.find({ where: { draft: true } }) await whatIf.find({ where: { draft: true } })
await whatIf.release() await whatIf.release()
@ -2009,7 +2032,7 @@ await base.release()
// Commit it for real — one atomic generation, with audit metadata // Commit it for real — one atomic generation, with audit metadata
await brain.transact( await brain.transact(
[{ op: 'add', type: NounType.Document, subtype: 'note', data: 'New feature' }], [{ op: 'add', type: NounType.Document, subtype: 'note', data: 'New feature', metadata: { draft: true } }],
{ meta: { author: 'dev@example.com', message: 'Add new feature' } } { meta: { author: 'dev@example.com', message: 'Add new feature' } }
) )
``` ```
@ -2115,7 +2138,8 @@ For the full taxonomy with all 169 types and their descriptions, see:
- **[Stage 3 CANONICAL Taxonomy](../STAGE3-CANONICAL-TAXONOMY.md)** - Complete list with categories - **[Stage 3 CANONICAL Taxonomy](../STAGE3-CANONICAL-TAXONOMY.md)** - Complete list with categories
- **[Noun-Verb Taxonomy Architecture](../architecture/noun-verb-taxonomy.md)** - Design rationale - **[Noun-Verb Taxonomy Architecture](../architecture/noun-verb-taxonomy.md)** - Design rationale
### Migration from ### Migration from pre-Stage-3 taxonomies
**Breaking Changes:** **Breaking Changes:**
- `NounType.Content` removed → Use `Document`, `Message`, or `InformationContent` - `NounType.Content` removed → Use `Document`, `Message`, or `InformationContent`
- `NounType.User` removed → Use `Person` or `Agent` - `NounType.User` removed → Use `Person` or `Agent`
@ -2161,7 +2185,6 @@ For the full taxonomy with all 169 types and their descriptions, see:
- **[Find System](../FIND_SYSTEM.md)** - Natural language find() details - **[Find System](../FIND_SYSTEM.md)** - Natural language find() details
- **[VFS Quick Start](../vfs/QUICK_START.md)** - Complete VFS documentation - **[VFS Quick Start](../vfs/QUICK_START.md)** - Complete VFS documentation
- **[Import Anything Guide](../guides/import-anything.md)** - CSV, Excel, PDF, URL imports - **[Import Anything Guide](../guides/import-anything.md)** - CSV, Excel, PDF, URL imports
- **[Cloud Deployment](../deployment/CLOUD_DEPLOYMENT_GUIDE.md)** - Production deployment
- **[Consistency Model](../concepts/consistency-model.md)** - The guarantees behind the Db API - **[Consistency Model](../concepts/consistency-model.md)** - The guarantees behind the Db API
- **[Snapshots & Time Travel](../guides/snapshots-and-time-travel.md)** - Backup, restore, what-if, audit recipes - **[Snapshots & Time Travel](../guides/snapshots-and-time-travel.md)** - Backup, restore, what-if, audit recipes

View file

@ -97,7 +97,7 @@ On `brain.update(entity)`, the engine reverses the old entity's contribution and
### Delete Handling ### Delete Handling
On `brain.delete(id)`, the engine reverses the entity's contribution: On `brain.remove(id)`, the engine reverses the entity's contribution:
- `count` and `sum` are decremented - `count` and `sum` are decremented
- `min`/`max` may become stale (marked in `staleMinMax` for lazy recompute) - `min`/`max` may become stale (marked in `staleMinMax` for lazy recompute)

View file

@ -1,359 +0,0 @@
# 🔍 Brainy 2.0 Augmentation System Architecture Audit (REVISED)
**Author**: Senior Architecture Review
**Date**: 2025-08-25
**Status**: 🟡 **WORKING BUT NEEDS MARKETPLACE FEATURES**
## Executive Summary
The augmentation system core execution is WORKING correctly through `AugmentationRegistry`. The system properly executes augmentations before/after operations. However, there's no discovery, installation, or marketplace integration for the brain-cloud registry vision.
---
## 🟢 What's Actually Working
### 1. Execution Mechanism ✅
The `AugmentationRegistry` class properly implements:
```typescript
async execute<T>(operation: string, params: any, mainOperation: () => Promise<T>): Promise<T>
```
- Chains augmentations correctly
- Respects timing (before/after/around)
- Handles operation filtering
- Works with all 27 augmentations
### 2. Registration System ✅
```typescript
brain.augmentations.register(augmentation)
```
- Two-phase initialization works (storage first)
- Context injection works
- Lifecycle management works
### 3. Clean Interface ✅
- 100% of augmentations use `BrainyAugmentation`
- `BaseAugmentation` provides solid foundation
- Proper TypeScript types
### 4. Auto-Configuration ✅
```typescript
new Brainy({
cache: true, // Auto-registers CacheAugmentation
index: true, // Auto-registers IndexAugmentation
storage: { type: 'filesystem', rootDirectory: './data' } // Auto-registers FileSystemStorageAugmentation
})
```
---
## 🟡 Missing for Marketplace Vision
### 1. No Package Discovery
**Current**: Manual registration only
```typescript
// Current (manual)
import { NotionSynapse } from './my-custom-synapse'
brain.augmentations.register(new NotionSynapse())
// Needed
await brain.discover('notion') // Search npm/brain-cloud
await brain.install('@soulcraft/notion-synapse')
```
### 2. No Installation Mechanism
**Current**: Must be bundled at build time
**Needed**: Dynamic installation
```typescript
interface AugmentationMarketplace {
search(query: string): Promise<Package[]>
install(packageId: string): Promise<void>
uninstall(packageId: string): Promise<void>
listInstalled(): Promise<Package[]>
checkUpdates(): Promise<Update[]>
}
```
### 3. No Brain Cloud Registry Client
**Current**: No registry concept
**Needed**: Registry integration
```typescript
class BrainCloudRegistry {
private apiUrl = 'https://api.soulcraft.com/brain-cloud'
async search(query: string): Promise<AugmentationPackage[]> {
const response = await fetch(`${this.apiUrl}/augmentations/search?q=${query}`)
return response.json()
}
async getPackage(id: string): Promise<AugmentationPackage> {
const response = await fetch(`${this.apiUrl}/augmentations/${id}`)
return response.json()
}
}
```
### 4. No License Management
**Current**: All augmentations free/bundled
**Needed**: License verification
```typescript
interface LicenseManager {
verify(packageId: string, licenseKey: string): Promise<boolean>
activate(packageId: string, licenseKey: string): Promise<void>
deactivate(packageId: string): Promise<void>
getStatus(packageId: string): Promise<LicenseStatus>
}
```
### 5. No Version Management
**Current**: No versioning
**Needed**: Semver support
```typescript
interface VersionManager {
checkCompatibility(pkg: Package, brainyVersion: string): boolean
resolveConflicts(packages: Package[]): Package[]
upgrade(packageId: string, toVersion: string): Promise<void>
}
```
---
## 📋 Implementation Plan for Marketplace
### Phase 1: Local Package Discovery (1 week)
```typescript
class LocalPackageDiscovery {
async discover(): Promise<Package[]> {
// 1. Search node_modules for brainy augmentations
const packages = await glob('node_modules/@*/package.json')
// 2. Filter for brainy augmentations
return packages.filter(pkg => pkg.brainy?.type === 'augmentation')
}
async load(packageId: string): Promise<BrainyAugmentation> {
// Dynamic import
const module = await import(packageId)
return new module.default()
}
}
```
### Phase 2: NPM Integration (1 week)
```typescript
class NPMRegistry {
async search(query: string): Promise<Package[]> {
// Search npm for packages with brainy keyword
const response = await fetch(
`https://registry.npmjs.org/-/v1/search?text=${query}+keywords:brainy-augmentation`
)
return response.json()
}
async install(packageId: string): Promise<void> {
// Use npm programmatically
await exec(`npm install ${packageId}`)
// Auto-register after install
const aug = await this.load(packageId)
this.brain.augmentations.register(aug)
}
}
```
### Phase 3: Brain Cloud Registry (2 weeks)
```typescript
class BrainCloudMarketplace {
private registry = new BrainCloudRegistry()
private licenses = new LicenseManager()
private installer = new AugmentationInstaller()
async browse(category?: string): Promise<MarketplaceListing[]> {
const packages = await this.registry.list(category)
return packages.map(pkg => ({
...pkg,
installed: this.isInstalled(pkg.id),
licensed: this.isLicensed(pkg.id),
updates: this.hasUpdates(pkg.id)
}))
}
async purchase(packageId: string): Promise<void> {
// 1. Process payment
const license = await this.processPayment(packageId)
// 2. Activate license
await this.licenses.activate(packageId, license)
// 3. Install package
await this.install(packageId)
}
}
```
### Phase 4: Developer Tools (1 week)
```typescript
// CLI for augmentation development
class AugmentationCLI {
async create(name: string): Promise<void> {
// Scaffold new augmentation project
await this.scaffold(name, 'augmentation-template')
}
async test(path: string): Promise<void> {
// Test augmentation locally
const aug = await this.load(path)
await this.runTests(aug)
}
async publish(path: string): Promise<void> {
// Publish to brain-cloud
const pkg = await this.package(path)
await this.registry.publish(pkg)
}
}
```
---
## 🏗️ Recommended Architecture
### 1. Augmentation Package Structure
```json
{
"name": "@soulcraft/notion-synapse",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"brainy": {
"type": "augmentation",
"class": "NotionSynapse",
"timing": "after",
"operations": ["addNoun", "updateNoun"],
"priority": 20,
"license": "premium",
"price": 9.99,
"compatibility": ">=2.0.0",
"dependencies": []
},
"keywords": ["brainy-augmentation", "notion", "sync"]
}
```
### 2. Installation Flow
```typescript
// User flow
await brain.marketplace.search('notion')
// Returns: [@soulcraft/notion-synapse, @community/notion-sync, ...]
await brain.marketplace.install('@soulcraft/notion-synapse')
// 1. Check license (prompt for purchase if needed)
// 2. Check compatibility
// 3. Install dependencies
// 4. Download package
// 5. Load augmentation
// 6. Register with brain
// 7. Initialize
// Now it's working!
brain.augmentations.list()
// [..., { name: '@soulcraft/notion-synapse', enabled: true }]
```
### 3. Discovery UI
```typescript
// Web UI component
<AugmentationMarketplace>
<SearchBar />
<Categories>
<Category name="Storage" count={12} />
<Category name="Sync" count={8} />
<Category name="AI" count={15} />
</Categories>
<Results>
<AugmentationCard
name="Notion Synapse"
author="Soulcraft"
rating={4.8}
installs={1200}
price={9.99}
onInstall={...}
/>
</Results>
</AugmentationMarketplace>
```
---
## 🎯 Priority for 2.0 Release
### Must Have (Release Blockers)
- ✅ Working execution (DONE)
- ✅ Clean interface (DONE)
- ✅ Documentation (DONE)
- ⏳ Fix augmentationPipeline.ts removal
- ⏳ Test all 27 augmentations work
### Nice to Have (2.0.x)
- Local package discovery
- NPM integration
- Basic CLI tools
### Future (2.1+)
- Brain Cloud Registry
- License management
- Payment processing
- Marketplace UI
- Developer portal
---
## 📊 Current State Assessment
| Component | Status | Notes |
|-----------|--------|-------|
| Core Execution | ✅ Working | AugmentationRegistry.execute() works |
| Registration | ✅ Working | Manual registration works |
| Auto-Config | ✅ Working | Cache, index, storage auto-register |
| Lifecycle | ✅ Working | Init, execute, shutdown work |
| Discovery | ❌ Missing | No package discovery |
| Installation | ❌ Missing | No dynamic installation |
| Marketplace | ❌ Missing | No registry client |
| Licensing | ❌ Missing | No license management |
| Versioning | ❌ Missing | No version checks |
---
## 💡 Recommendations
### For 2.0 Release
1. **Ship with manual registration** - It works!
2. **Document how to create augmentations** - Critical for adoption
3. **Create 2-3 example augmentations** - Show the patterns
4. **Add basic CLI for testing** - Help developers
### For 2.1 (Q1 2025)
1. **Add NPM discovery** - Find installed augmentations
2. **Dynamic loading** - Import augmentations at runtime
3. **Basic marketplace API** - List available augmentations
4. **Version checking** - Ensure compatibility
### For 3.0 (Q2 2025)
1. **Full marketplace** - Browse, search, install
2. **Payment integration** - Premium augmentations
3. **Developer portal** - Publish augmentations
4. **Enterprise features** - Private registries
---
## ✅ Good News Summary
The augmentation system WORKS! The core architecture is solid:
- Execution mechanism is correct
- Registration works
- Lifecycle management works
- All 27 augmentations function properly
What's missing is the marketplace/discovery layer, which can be added incrementally without breaking the core system. The 2.0 release can ship with manual augmentation registration, and the marketplace features can be added in 2.1+.
**Recommendation: Ship 2.0 with current system, add marketplace in 2.1**

View file

@ -195,7 +195,7 @@ tx-log line is appended last (advisory). Crash recovery on open discards any
Two write classes share the generation clock: Two write classes share the generation clock:
- **Single-operation writes** (`add`/`update`/`delete`/`relate` outside - **Single-operation writes** (`add`/`update`/`remove`/`relate` outside
`transact()`) bump `generation.json` so watermarks and `_rev` CAS stay sound, `transact()`) bump `generation.json` so watermarks and `_rev` CAS stay sound,
but write **no** history — they are not visible to `db.since()` and remain but write **no** history — they are not visible to `db.since()` and remain
visible through earlier pins. visible through earlier pins.

View file

@ -239,37 +239,30 @@ await brain.storage.withLock('resource-id', async () => {
## Migration and Backup ## Migration and Backup
### Export Data Backup and restore go through the Db API — see
[Snapshots & Time Travel](../guides/snapshots-and-time-travel.md) for the full
recipe book.
### Snapshot (backup)
```typescript ```typescript
// Export entire database // Instant, self-contained snapshot (hard links on filesystem storage)
const backup = await brain.export({ const db = brain.now()
format: 'json', await db.persist('/backups/2026-06-11')
includeVectors: true, await db.release()
includeIndexes: false
})
``` ```
### Import Data ### Restore
```typescript ```typescript
// Import from backup // Replace the store's entire state from a snapshot (destructive — confirm required)
await brain.import(backup, { await brain.restore('/backups/2026-06-11', { confirm: true })
mode: 'merge', // or 'replace'
validateSchema: true
})
``` ```
### Storage Migration ### Move to a new directory
```typescript ```typescript
// Migrate between storage types // A snapshot directory is a complete store: restore it into a fresh brain
const oldBrain = new Brainy({ storage: { type: 'filesystem', rootDirectory: './old' } }) const brain = new Brainy({ storage: { type: 'filesystem', rootDirectory: './new' } })
const newBrain = new Brainy({ storage: { type: 'filesystem', rootDirectory: './new' } }) await brain.init()
await brain.restore('/backups/2026-06-11', { confirm: true })
await oldBrain.init()
await newBrain.init()
// Transfer all data
const data = await oldBrain.export()
await newBrain.import(data)
``` ```
## Performance Tuning ## Performance Tuning

View file

@ -1,11 +1,11 @@
--- ---
title: Zero Configuration title: Zero Configuration
slug: concepts/zero-config slug: concepts/zero-config
public: true public: false
category: concepts category: concepts
template: concept template: concept
order: 3 order: 3
description: Brainy auto-detects storage, initializes embeddings, and builds indexes — no configuration required. Works in Node.js, Bun, OPFS, and cloud environments. description: Brainy auto-detects storage, initializes embeddings, and builds indexes — no configuration required. Works in Node.js and Bun (server-only since 8.0).
next: next:
- getting-started/installation - getting-started/installation
- guides/storage-adapters - guides/storage-adapters
@ -13,7 +13,13 @@ next:
# Zero Configuration & Auto-Adaptation # Zero Configuration & Auto-Adaptation
> **Current Status**: Basic zero-config is fully functional. Advanced auto-adaptation features are in development. > **Status (8.0):** This document predates Brainy 8.0 and needs a rewrite before
> republication. Large parts describe storage backends and environments that 8.0
> removed (browser/OPFS/IndexedDB, edge KV, S3) or features that were never built
> (model auto-selection, workload detection). Brainy 8.0 is server-only (Node.js/Bun)
> with two storage adapters: `memory` and `filesystem` — see
> [Storage Adapters](../concepts/storage-adapters.md) for the accurate story.
> Basic zero-config (`new Brainy()` with auto-selected storage) works as described.
## Overview ## Overview

View file

@ -1,396 +0,0 @@
# 🔌 Brainy Augmentations Complete Reference
> **All augmentations that power Brainy's extensibility - with locations, usage, and examples**
>
> **⚠️ Update**: Updated for metadata structure changes and billion-scale optimizations
## Quick Start
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy({
// Augmentations auto-configure based on environment
storage: { type: 'auto', rootDirectory: './brainy-data' }, // Storage augmentation
cache: true, // Cache augmentation
index: true // Index augmentation
})
await brain.init() // Augmentations initialize automatically
```
## Augmentation Architecture
### Key Improvements for Billion-Scale Performance
1. **Metadata/Vector Separation**: Augmentations now work with separated metadata and vectors
- Metadata stored separately from vector data
- 99.2% memory reduction for type tracking
- Two-file storage pattern for optimal I/O
2. **Type System Enforcement**: All metadata requires type fields
- `NounMetadata` requires `noun: NounType`
- `VerbMetadata` requires `verb: VerbType`
- Type inference system available as public API
3. **Storage Adapter Pattern**: Internal vs public method distinction
- `_methods`: Return pure structures (HNSWNoun, HNSWVerb)
- Public methods: Return WithMetadata types
- MetadataEnforcer Proxy ensures proper access
### What This Means for Augmentation Users
**✅ If you use built-in augmentations**: No changes needed! They're all updated.
**⚠️ If you created custom storage augmentations**: Update your storage adapter to:
- Wrap metadata with required `noun`/`verb` fields
- Follow the internal/public method pattern
- Use two-file storage approach
**⚠️ If you access relationship data**: Change `verb.type` to `verb.verb`
## Core Concepts
### What are Augmentations?
Augmentations are modular extensions that add functionality to Brainy without cluttering the core API. They follow a unified interface and can be:
- **Auto-enabled**: Based on configuration (cache, index, storage)
- **Manually registered**: For custom functionality
- **Chained**: Multiple augmentations work together seamlessly
- **Billion-scale ready**: Optimized for datasets with billions of nouns and verbs
### Augmentation Lifecycle
1. **Registration**: Augmentations register before init()
2. **Initialization**: Two-phase init (storage first, then others)
3. **Execution**: Hook into operations (before/after/both)
4. **Shutdown**: Clean teardown on brain.shutdown()
---
## Storage Augmentations
### MemoryStorageAugmentation
**Location**: `src/augmentations/storageAugmentations.ts`
**Auto-enabled**: When `storage: { type: 'memory' }` or in test environments
**Purpose**: In-memory storage for testing and temporary data
```typescript
const brain = new Brainy({ storage: { type: 'memory' } })
```
### FileSystemStorageAugmentation
**Location**: `src/augmentations/storageAugmentations.ts`
**Auto-enabled**: When `storage: { type: 'filesystem' }` or Node.js detected
**Purpose**: Persistent file-based storage for Node.js applications
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', rootDirectory: './data' }
})
```
For off-site backup, snapshot `rootDirectory` from your scheduler using `gsutil rsync`, `aws s3 sync`, `rclone`, or `tar` — there are no cloud storage augmentations.
### StorageAugmentation (base)
**Location**: `src/augmentations/storageAugmentation.ts`
**Purpose**: Base class for custom storage implementations
### DynamicStorageAugmentation
**Location**: `src/augmentations/storageAugmentation.ts`
**Purpose**: Runtime storage adapter switching
---
## Performance Augmentations (7 total)
### CacheAugmentation
**Location**: `src/augmentations/cacheAugmentation.ts`
**Auto-enabled**: When `cache: true` (default)
**Purpose**: LRU cache for search results and frequent queries
```typescript
brain.clearCache() // Exposed via API
brain.getCacheStats() // Cache hit/miss statistics
```
### IndexAugmentation
**Location**: `src/augmentations/indexAugmentation.ts`
**Auto-enabled**: When `index: true` (default)
**Purpose**: Metadata indexing for O(1) field lookups
```typescript
brain.rebuildMetadataIndex() // Exposed via API
// Enables fast where queries:
brain.find({ where: { category: 'tech' } })
```
### MetricsAugmentation
**Location**: `src/augmentations/metricsAugmentation.ts`
**Auto-enabled**: Always active
**Purpose**: Performance metrics and statistics collection
```typescript
brain.getStats() // Comprehensive metrics
```
### MonitoringAugmentation
**Location**: `src/augmentations/monitoringAugmentation.ts`
**Manual**: Register for detailed monitoring
**Purpose**: Real-time performance monitoring and alerts
### BatchProcessingAugmentation
**Location**: `src/augmentations/batchProcessingAugmentation.ts`
**Auto-enabled**: For batch operations
**Purpose**: Optimizes bulk add/update/delete operations
```typescript
brain.addNouns([...]) // Automatically batched
```
### RequestDeduplicatorAugmentation
**Location**: `src/augmentations/requestDeduplicatorAugmentation.ts`
**Auto-enabled**: Always active
**Purpose**: Prevents duplicate concurrent operations
---
## Data Integrity Augmentations (3 total)
**Auto-enabled**: When `wal: true`
**Purpose**: Write-ahead logging for crash recovery
```typescript
const brain = new Brainy({ wal: true })
// Automatic recovery on restart after crash
```
### EntityRegistryAugmentation
**Location**: `src/augmentations/entityRegistryAugmentation.ts`
**Auto-enabled**: For streaming operations
**Purpose**: High-speed deduplication for real-time data
```typescript
// Prevents duplicate entities in streaming scenarios
brain.add(data) // Automatically deduplicated
```
### AutoRegisterEntitiesAugmentation
**Location**: `src/augmentations/entityRegistryAugmentation.ts`
**Manual**: For automatic entity discovery
**Purpose**: Auto-discovers and registers entities from data
---
## Intelligence Augmentations (2 total)
### NeuralImportAugmentation
**Location**: `src/augmentations/neuralImport.ts`
**Manual**: Via `brain.neuralImport()`
**Purpose**: AI-powered smart data import
```typescript
const result = await brain.neuralImport(data, {
confidenceThreshold: 0.7,
autoApply: true
})
// Automatically detects entities and relationships
```
### IntelligentVerbScoringAugmentation
**Location**: `src/augmentations/intelligentVerbScoringAugmentation.ts`
**Auto-enabled**: When verbs are used
**Purpose**: ML-based relationship strength scoring
```typescript
brain.verbScoring.train(feedback)
brain.verbScoring.getScore(verbId)
```
---
## Communication Augmentations (4 total)
### APIServerAugmentation
**Location**: `src/augmentations/apiServerAugmentation.ts`
**Manual**: For server deployments
**Purpose**: REST/WebSocket/MCP API server
```typescript
const augmentation = new APIServerAugmentation()
await brain.registerAugmentation(augmentation)
// Exposes full Brainy API over network
```
### WebSocketConduitAugmentation
**Location**: `src/augmentations/conduitAugmentations.ts`
**Manual**: For Brainy-to-Brainy sync
**Purpose**: Real-time sync between Brainy instances
```typescript
const conduit = new WebSocketConduitAugmentation()
await conduit.establishConnection('ws://other-brain')
```
### ServerSearchConduitAugmentation
**Location**: `src/augmentations/serverSearchAugmentations.ts`
**Manual**: For client-server search
**Purpose**: Search remote Brainy instance, cache locally
### ServerSearchActivationAugmentation
**Location**: `src/augmentations/serverSearchAugmentations.ts`
**Manual**: Works with ServerSearchConduit
**Purpose**: Triggers and manages server search operations
---
## External Integration (2 total)
### SynapseAugmentation (base)
**Location**: `src/augmentations/synapseAugmentation.ts`
**Purpose**: Base class for external platform integrations
```typescript
// Example: NotionSynapse, SlackSynapse, etc.
class NotionSynapse extends SynapseAugmentation {
async fetchData() { /* Notion API calls */ }
async pushData() { /* Sync to Notion */ }
}
```
### ExampleFileSystemSynapse
**Location**: `src/augmentations/synapseAugmentation.ts`
**Purpose**: Example implementation for file system sync
---
## Augmentation Configuration
### Auto-Configuration
```typescript
const brain = new Brainy({
// These auto-register augmentations:
storage: { type: 'auto', rootDirectory: './brainy-data' }, // Storage augmentation
cache: true, // Cache augmentation
index: true, // Index augmentation
metrics: true // Metrics augmentation
})
```
### Manual Registration
```typescript
const brain = new Brainy()
// Register before init()
const customAug = new MyCustomAugmentation()
await brain.registerAugmentation(customAug)
await brain.init()
```
### Creating Custom Augmentations
```typescript
import { BaseAugmentation } from '@soulcraft/brainy'
class MyAugmentation extends BaseAugmentation {
readonly name = 'my-augmentation'
readonly timing = 'after' // before | after | both
readonly operations = ['addNoun', 'search'] // Which ops to hook
readonly priority = 10 // Execution order (lower = earlier)
protected async onInit(): Promise<void> {
// Initialize your augmentation
}
async execute<T>(
operation: string,
params: any,
context?: AugmentationContext
): Promise<T | void> {
// Your augmentation logic
if (operation === 'addNoun') {
console.log('Noun added:', params)
}
}
protected async onShutdown(): Promise<void> {
// Cleanup
}
}
```
---
## Augmentation Timing & Priority
### Timing Options
- **`before`**: Runs before the operation (can modify params)
- **`after`**: Runs after the operation (can see results)
- **`both`**: Runs before AND after
### Priority (lower = earlier)
1. Storage augmentations (priority: 0)
2. Cache/Index augmentations (priority: 5-10)
3. Monitoring/Metrics (priority: 15-20)
4. Conduits/Synapses (priority: 20-30)
---
## Key Integration Points
### Where Augmentations Hook In
**Brainy Constructor**:
- Storage augmentations register based on config
- Cache/Index augmentations auto-register if enabled
**brain.init()**:
- Two-phase initialization (storage first, then others)
- Augmentations can access brain instance via context
**Operations** (addNoun, search, etc.):
- Augmentations execute based on timing and operations filter
- Can modify params (before) or see results (after)
**brain.shutdown()**:
- All augmentations cleaned up in reverse order
---
## Performance Impact
Most augmentations have minimal overhead:
- **Cache**: ~1ms per search (saves 10-100ms on hits)
- **Index**: ~1ms per operation (saves 100ms+ on queries)
- **Metrics**: <1ms per operation
- **Storage**: Varies by adapter (memory: 0ms, filesystem: 1-10ms)
---
## Best Practices
1. **Let auto-configuration work**: Most apps need zero manual config
2. **Storage first**: Always configure storage before other augmentations
3. **Use built-in augmentations**: They're optimized and battle-tested
4. **Custom augmentations**: Extend BaseAugmentation for consistency
5. **Respect timing**: Use 'before' to modify, 'after' to observe
6. **Mind priority**: Lower numbers execute first
---
## Troubleshooting
### Augmentation not working?
```typescript
// Check if registered
brain.listAugmentations()
// Check if enabled
brain.isAugmentationEnabled('cache')
// Enable/disable at runtime
brain.enableAugmentation('cache')
brain.disableAugmentation('cache')
```
### Performance issues?
```typescript
// Check augmentation overhead
const stats = brain.getStats()
console.log(stats.augmentations)
// Disable non-critical augmentations
brain.disableAugmentation('monitoring')
```
---
---
*Augmentations make Brainy infinitely extensible while keeping the core API clean and simple!*

View file

@ -1,620 +0,0 @@
# Augmentation Configuration System
**Version**: 2.0.0
**Status**: Production Ready
## Overview
The Brainy Augmentation Configuration System provides a VSCode-style extension architecture with multiple configuration sources, schema validation, and tool discovery. This system maintains Brainy's zero-config philosophy while enabling sophisticated enterprise configuration management.
## Table of Contents
- [Quick Start](#quick-start)
- [Configuration Sources](#configuration-sources)
- [Creating Configurable Augmentations](#creating-configurable-augmentations)
- [Configuration Discovery](#configuration-discovery)
- [Runtime Configuration](#runtime-configuration)
- [Environment Variables](#environment-variables)
- [Configuration Files](#configuration-files)
- [CLI Commands](#cli-commands)
- [Tool Integration](#tool-integration)
- [Migration Guide](#migration-guide)
## Quick Start
### Using an Augmentation with Configuration
```typescript
import { Brainy } from '@soulcraft/brainy'
// Zero-config (uses defaults)
const brain = new Brainy()
// With custom configuration
immediateWrites: true,
checkpointInterval: 300000 // 5 minutes
}))
```
### Configuring via Environment Variables
```bash
export BRAINY_AUG_CACHE_TTL=600000
```
### Configuring via Files
Create a `.brainyrc` file in your project root:
```json
{
"augmentations": {
"wal": {
"enabled": true,
"immediateWrites": true,
"maxSize": 20971520
},
"cache": {
"ttl": 600000,
"maxSize": 2000
}
}
}
```
## Configuration Sources
Configuration is resolved in the following priority order (highest to lowest):
1. **Runtime Updates** - Dynamic configuration changes via API
2. **Constructor Parameters** - Code-time configuration
3. **Environment Variables** - `BRAINY_AUG_<NAME>_<KEY>`
4. **Configuration Files** - `.brainyrc`, `brainy.config.json`
5. **Schema Defaults** - Default values from manifest
### Resolution Example
```typescript
// Schema default
{ maxSize: 10485760 }
// File configuration (.brainyrc)
{ maxSize: 20971520 }
// Environment variable
// Constructor parameter
// Final resolved value: 41943040 (constructor wins)
```
## Creating Configurable Augmentations
### Step 1: Extend ConfigurableAugmentation
```typescript
import { ConfigurableAugmentation, AugmentationManifest } from '@soulcraft/brainy'
export class MyAugmentation extends ConfigurableAugmentation {
name = 'my-augmentation'
timing = 'around' as const
metadata = 'none' as const
operations = ['search', 'add']
priority = 50
constructor(config?: MyConfig) {
super(config) // Handles configuration resolution
}
// Required: Provide manifest for discovery
getManifest(): AugmentationManifest {
return {
id: 'my-augmentation',
name: 'My Augmentation',
version: '1.0.0',
description: 'Does something amazing',
category: 'performance',
configSchema: {
type: 'object',
properties: {
enabled: {
type: 'boolean',
default: true,
description: 'Enable this augmentation'
},
threshold: {
type: 'number',
default: 100,
minimum: 1,
maximum: 1000,
description: 'Processing threshold'
}
}
}
}
}
// Optional: Handle runtime configuration changes
protected async onConfigChange(newConfig: MyConfig, oldConfig: MyConfig): Promise<void> {
if (newConfig.threshold !== oldConfig.threshold) {
// React to threshold change
this.updateThreshold(newConfig.threshold)
}
}
async execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T> {
if (!this.config.enabled) {
return next()
}
// Your augmentation logic here
return next()
}
}
```
### Step 2: Define Configuration Interface
```typescript
interface MyConfig {
enabled?: boolean
threshold?: number
mode?: 'fast' | 'balanced' | 'thorough'
}
```
### Step 3: Add JSON Schema in Manifest
```typescript
configSchema: {
type: 'object',
properties: {
enabled: {
type: 'boolean',
default: true,
description: 'Enable augmentation'
},
threshold: {
type: 'number',
default: 100,
minimum: 1,
maximum: 1000,
description: 'Processing threshold'
},
mode: {
type: 'string',
default: 'balanced',
enum: ['fast', 'balanced', 'thorough'],
description: 'Processing mode'
}
},
required: [],
additionalProperties: false
}
```
## Configuration Discovery
The Discovery API allows tools to discover and configure augmentations dynamically:
```typescript
import { AugmentationDiscovery } from '@soulcraft/brainy'
const discovery = new AugmentationDiscovery(brain.augmentations)
// Discover all augmentations with manifests
const listings = await discovery.discover({
includeConfig: true,
includeSchema: true
})
// Get configuration schema
const schema = await discovery.getConfigSchema('wal')
// Validate configuration
const validation = await discovery.validateConfig('wal', {
enabled: true,
maxSize: 'invalid' // Will fail validation
})
// Update configuration at runtime
await discovery.updateConfig('wal', {
checkpointInterval: 120000
})
```
## Runtime Configuration
### Update Configuration Dynamically
```typescript
// Get augmentation
const wal = brain.augmentations.get('wal')
// Update configuration
await wal.updateConfig({
checkpointInterval: 300000
})
// Get current configuration
const config = wal.getConfig()
```
### React to Configuration Changes
```typescript
class MyAugmentation extends ConfigurableAugmentation {
protected async onConfigChange(newConfig: any, oldConfig: any): Promise<void> {
// Stop old processes
if (oldConfig.enabled && !newConfig.enabled) {
await this.stop()
}
// Start new processes
if (!oldConfig.enabled && newConfig.enabled) {
await this.start()
}
// Update settings
if (newConfig.interval !== oldConfig.interval) {
this.rescheduleTimer(newConfig.interval)
}
}
}
```
## Environment Variables
### Naming Convention
```bash
BRAINY_AUG_<AUGMENTATION_ID>_<CONFIG_KEY>=value
```
### Examples
```bash
# Cache augmentation
BRAINY_AUG_CACHE_ENABLED=true
BRAINY_AUG_CACHE_MAX_SIZE=2000
BRAINY_AUG_CACHE_TTL=600000
# Complex values (JSON)
BRAINY_AUG_MYAUG_FILTERS='["*.js","*.ts"]'
BRAINY_AUG_MYAUG_OPTIONS='{"deep":true,"follow":false}'
```
### Docker Example
```dockerfile
ENV BRAINY_AUG_CACHE_TTL=600000
```
## Configuration Files
### File Locations (Priority Order)
1. `.brainyrc` (current directory)
2. `.brainyrc.json` (current directory)
3. `brainy.config.json` (current directory)
4. `~/.brainy/config.json` (user home)
5. `~/.brainyrc` (user home)
### File Format
```json
{
"augmentations": {
"wal": {
"enabled": true,
"immediateWrites": true,
"maxSize": 20971520,
"checkpointInterval": 300000
},
"cache": {
"enabled": true,
"maxSize": 2000,
"ttl": 600000
},
"metrics": {
"enabled": false
}
}
}
```
### Per-Environment Configuration
```json
{
"augmentations": {
"wal": {
"development": {
"enabled": true,
"immediateWrites": true,
"maxSize": 5242880
},
"production": {
"enabled": true,
"immediateWrites": false,
"maxSize": 104857600,
"checkpointInterval": 60000
}
}
}
}
```
## CLI Commands
### List Augmentations with Configuration
```bash
# Show all augmentations with config status
brainy augment list --detailed
# Show configuration for specific augmentation
brainy augment config wal
# Set configuration value
brainy augment config wal --set immediateWrites=true
# Show environment variable names
brainy augment config wal --env
# Export configuration schema
brainy augment schema wal > wal-schema.json
# Validate configuration file
brainy augment validate --file config.json
```
### Interactive Configuration
```bash
# Interactive configuration wizard
brainy augment configure wal
? Operation mode?
Performance (immediate writes)
Durability (synchronous writes)
Custom
? Maximum log size? (10MB) 20MB
? Checkpoint interval? (1 minute) 5 minutes
Configuration saved to .brainyrc
```
## Tool Integration
### Brain-Cloud Explorer UI
```typescript
// Auto-generate configuration form from schema
const ConfigurationUI = ({ augmentationId }) => {
const [manifest, setManifest] = useState(null)
const [config, setConfig] = useState({})
useEffect(() => {
// Fetch manifest with schema
fetch(`/api/augmentations/${augmentationId}/manifest`)
.then(res => res.json())
.then(setManifest)
// Get current configuration
discovery.getConfig(augmentationId)
.then(setConfig)
}, [augmentationId])
const handleSave = async (newConfig) => {
// Validate configuration
const validation = await fetch(`/api/augmentations/${augmentationId}/validate`, {
method: 'POST',
body: JSON.stringify(newConfig)
}).then(res => res.json())
if (validation.valid) {
// Apply configuration
await discovery.updateConfig(augmentationId, newConfig)
}
}
// Render form based on schema
return <SchemaForm
schema={manifest?.configSchema}
values={config}
onSubmit={handleSave}
/>
}
```
### VS Code Extension
```json
// package.json contribution points
{
"contributes": {
"configuration": {
"title": "Brainy Augmentations",
"properties": {
"brainy.augmentations.wal.enabled": {
"type": "boolean",
"default": true,
},
"brainy.augmentations.wal.maxSize": {
"type": "number",
"default": 10485760,
}
}
}
}
}
```
## Migration Guide
### Migrating from BaseAugmentation
**Before:**
```typescript
export class MyAugmentation extends BaseAugmentation {
constructor(config: MyConfig = {}) {
super()
this.config = {
enabled: config.enabled ?? true,
threshold: config.threshold ?? 100
}
}
// No manifest
// No config discovery
// No runtime updates
}
```
**After:**
```typescript
export class MyAugmentation extends ConfigurableAugmentation {
constructor(config?: MyConfig) {
super(config) // Config resolution handled automatically
}
getManifest(): AugmentationManifest {
return {
id: 'my-augmentation',
name: 'My Augmentation',
version: '1.0.0',
description: 'Does something amazing',
category: 'performance',
configSchema: {
type: 'object',
properties: {
enabled: { type: 'boolean', default: true },
threshold: { type: 'number', default: 100 }
}
}
}
}
// Optional: Handle config changes
protected async onConfigChange(newConfig: MyConfig, oldConfig: MyConfig): Promise<void> {
// React to changes
}
}
```
### Backwards Compatibility
The system maintains full backwards compatibility:
1. **BaseAugmentation still works** - Existing augmentations continue to function
2. **Constructor config still works** - Existing configuration patterns preserved
3. **Zero-config still works** - Defaults are applied automatically
4. **Progressive enhancement** - Add features as needed
## Best Practices
### 1. Always Provide Defaults
```typescript
configSchema: {
properties: {
enabled: {
type: 'boolean',
default: true, // Always provide defaults
description: 'Enable this feature'
}
}
}
```
### 2. Use Descriptive Configuration Keys
```typescript
// Good
checkpointInterval: 60000
// Bad
ci: 60000
```
### 3. Validate Configuration
```typescript
protected async onConfigChange(newConfig: any, oldConfig: any): Promise<void> {
// Validate before applying
if (newConfig.maxSize < 1048576) {
throw new Error('maxSize must be at least 1MB')
}
// Apply changes
this.maxSize = newConfig.maxSize
}
```
### 4. Document Environment Variables
```typescript
/**
* Environment Variables:
* - BRAINY_AUG_MYAUG_ENABLED: Enable augmentation (boolean)
* - BRAINY_AUG_MYAUG_THRESHOLD: Processing threshold (number)
* - BRAINY_AUG_MYAUG_MODE: Processing mode (fast|balanced|thorough)
*/
```
### 5. Provide Configuration Examples
```typescript
configExamples: [
{
name: 'Production',
description: 'Optimized for production use',
config: {
enabled: true,
mode: 'thorough',
threshold: 500
}
},
{
name: 'Development',
description: 'Lightweight for development',
config: {
enabled: true,
mode: 'fast',
threshold: 10
}
}
]
```
## Troubleshooting
### Configuration Not Loading
1. Check file locations and names
2. Verify JSON syntax in config files
3. Check environment variable names (case-sensitive)
4. Use `brainy augment config <name> --debug` to see resolution
### Validation Errors
1. Check schema requirements
2. Verify data types match schema
3. Check minimum/maximum constraints
4. Use discovery API to validate before applying
### Runtime Updates Not Working
1. Ensure augmentation extends ConfigurableAugmentation
2. Implement onConfigChange if needed
3. Check for validation errors
4. Verify augmentation is initialized
## API Reference
See the [Discovery API Documentation](./discovery-api.md) for complete API details.
## Examples
See the [examples directory](../../examples/augmentation-config/) for complete working examples.

View file

@ -1,532 +0,0 @@
# 🛠️ Brainy Augmentation Developer Guide
> **How to create, test, and use augmentations in Brainy**
>
> **⚠️ Update**: This guide has been updated with breaking changes for metadata structure and type system improvements.
## Migration Guide
### What Changed?
1. **Metadata Structure**: All metadata now requires type fields (`noun` or `verb`)
2. **Property Rename**: `verb.type``verb.verb` for relationships
3. **Two-File Storage**: Vectors and metadata stored separately for performance
4. **Return Types**: Storage methods distinguish between internal (pure) and public (WithMetadata) returns
### Migration Checklist
- [ ] Update metadata creation to include required `noun` field
- [ ] Change `verb.type` to `verb.verb` in all relationship code
- [ ] Update storage adapter methods to follow internal/public pattern
- [ ] Ensure metadata access uses correct structure
### Quick Migration Example
```typescript
// ❌ v3.x
const verb = {
type: 'relatedTo',
sourceId: 'a',
targetId: 'b'
}
if (verb.type === 'relatedTo') { ... }
// ✅ Current
const verb = {
verb: 'relatedTo',
sourceId: 'a',
targetId: 'b'
}
const metadata: VerbMetadata = {
verb: 'relatedTo',
sourceId: 'a',
targetId: 'b'
}
if (verb.verb === 'relatedTo') { ... }
```
## Quick Start: Your First Augmentation
```typescript
import { BaseAugmentation, BrainyAugmentation, AugmentationContext } from '@soulcraft/brainy'
export class MyFirstAugmentation extends BaseAugmentation {
readonly name = 'my-first-augmentation'
readonly timing = 'after' as const // When to run: before | after | both
readonly operations = ['add'] as const // Which operations to hook
readonly priority = 10 // Execution order (lower = first)
protected async onInit(): Promise<void> {
// Initialize your augmentation
console.log('MyFirstAugmentation initialized!')
}
async execute<T = any>(
operation: string,
params: any,
context?: AugmentationContext
): Promise<T | void> {
// Your augmentation logic
if (operation === 'add') {
console.log('Noun added:', params.noun)
// Access metadata correctly
if (params.noun?.metadata) {
console.log('Noun type:', params.noun.metadata.noun) // Required field
}
// You can access the brain instance
const stats = await context?.brain.getStats()
console.log('Total nouns:', stats.totalNouns)
}
}
protected async onShutdown(): Promise<void> {
// Cleanup
console.log('MyFirstAugmentation shutting down')
}
}
```
## Using Your Augmentation
```typescript
import { Brainy } from '@soulcraft/brainy'
import { MyFirstAugmentation } from './my-first-augmentation'
const brain = new Brainy()
// Register before init()
brain.augmentations.register(new MyFirstAugmentation())
await brain.init()
// Now your augmentation runs automatically!
await brain.add('Hello World')
// Console: "Noun added: { id: '...', vector: [...], metadata: {} }"
```
---
## Augmentation Lifecycle
### 1. Registration Phase
```typescript
const aug = new MyAugmentation()
brain.augmentations.register(aug) // Before brain.init()!
```
### 2. Initialization Phase
```typescript
await brain.init() // Calls aug.initialize() internally
// Your onInit() method runs here
```
### 3. Execution Phase
```typescript
await brain.add('data') // Your execute() method runs
```
### 4. Shutdown Phase
```typescript
await brain.shutdown() // Your onShutdown() method runs
```
---
## Timing Options
### `before` - Modify Input
```typescript
class ValidationAugmentation extends BaseAugmentation {
readonly timing = 'before' as const
async execute<T>(operation: string, params: any): Promise<any> {
if (operation === 'add') {
// Validate and/or modify params
if (!params.content) {
throw new Error('Content required')
}
// Return modified params
return { ...params, validated: true }
}
}
}
```
### `after` - React to Results
```typescript
class LoggingAugmentation extends BaseAugmentation {
readonly timing = 'after' as const
async execute<T>(operation: string, params: any): Promise<void> {
if (operation === 'search') {
console.log(`Search for "${params.query}" returned ${params.result.length} results`)
}
// Don't return anything - just observe
}
}
```
### `both` - Before AND After
```typescript
class TimingAugmentation extends BaseAugmentation {
readonly timing = 'both' as const
private startTime?: number
async execute<T>(operation: string, params: any, context?: AugmentationContext): Promise<void> {
if (!this.startTime) {
// Before execution
this.startTime = Date.now()
} else {
// After execution
const duration = Date.now() - this.startTime
console.log(`${operation} took ${duration}ms`)
this.startTime = undefined
}
}
}
```
---
## Operation Hooks
### Core Operations You Can Hook
```typescript
readonly operations = [
'add', // Adding data
'update', // Updating data
'delete', // Deleting data
'get', // Retrieving data
'search', // Searching
'find', // Triple Intelligence queries
'relate', // Adding relationships
'unrelate', // Removing relationships
'clear', // Clearing data
'all' // Hook ALL operations
] as const
```
### Example: Multi-Operation Hook
```typescript
class AuditAugmentation extends BaseAugmentation {
readonly operations = ['add', 'update', 'delete'] as const
async execute<T>(operation: string, params: any): Promise<void> {
// Log all data modifications
await this.logToAuditTrail(operation, params)
}
}
```
---
## Accessing Brain Context
```typescript
class ContextAwareAugmentation extends BaseAugmentation {
async execute<T>(
operation: string,
params: any,
context?: AugmentationContext
): Promise<void> {
// Access the brain instance
const brain = context?.brain
if (!brain) return
// Use any brain method
const stats = await brain.getStats()
const size = await brain.size()
const results = await brain.search('query')
// Access other augmentations
const cache = brain.augmentations.get('cache')
if (cache) {
await cache.clear()
}
}
}
```
---
## Real-World Examples
### 1. Backup Augmentation
```typescript
class BackupAugmentation extends BaseAugmentation {
readonly name = 'backup'
readonly timing = 'after' as const
readonly operations = ['add', 'update', 'delete'] as const
readonly priority = 5
private changes = 0
private readonly backupThreshold = 100
async execute<T>(operation: string, params: any, context?: AugmentationContext): Promise<void> {
this.changes++
if (this.changes >= this.backupThreshold) {
await this.performBackup(context?.brain)
this.changes = 0
}
}
private async performBackup(brain?: any): Promise<void> {
if (!brain) return
// Cut an instant hard-link snapshot via the Db API
const snapshotPath = `/backups/auto-${Date.now()}`
const pin = brain.now()
try {
await pin.persist(snapshotPath)
} finally {
await pin.release()
}
console.log(`Automatic snapshot created: ${snapshotPath}`)
}
}
```
### 2. Rate Limiting Augmentation
```typescript
class RateLimitAugmentation extends BaseAugmentation {
readonly name = 'rate-limit'
readonly timing = 'before' as const
readonly operations = ['search', 'find'] as const
readonly priority = 100 // High priority - run first
private requests = new Map<string, number[]>()
private readonly limit = 100 // 100 requests
private readonly window = 60000 // per minute
async execute<T>(operation: string, params: any): Promise<void> {
const now = Date.now()
const key = params.userId || 'anonymous'
// Get request timestamps
const timestamps = this.requests.get(key) || []
// Remove old timestamps
const recent = timestamps.filter(t => now - t < this.window)
// Check limit
if (recent.length >= this.limit) {
throw new Error('Rate limit exceeded')
}
// Add current request
recent.push(now)
this.requests.set(key, recent)
}
}
```
### 3. Encryption Augmentation
```typescript
class EncryptionAugmentation extends BaseAugmentation {
readonly name = 'encryption'
readonly timing = 'both' as const
readonly operations = ['add', 'get'] as const
readonly priority = 90 // Run early
async execute<T>(operation: string, params: any): Promise<any> {
if (operation === 'add') {
// Encrypt before storing
if (params.metadata?.sensitive) {
params.content = await this.encrypt(params.content)
params.encrypted = true
}
return params
}
if (operation === 'get' && params.result?.encrypted) {
// Decrypt after retrieval
params.result.content = await this.decrypt(params.result.content)
delete params.result.encrypted
return params.result
}
}
}
```
---
## Testing Your Augmentation
```typescript
import { describe, it, expect } from 'vitest'
import { Brainy } from '@soulcraft/brainy'
import { MyAugmentation } from './my-augmentation'
describe('MyAugmentation', () => {
it('should hook into addNoun', async () => {
const brain = new Brainy({ storage: 'memory' })
const aug = new MyAugmentation()
// Spy on the execute method
const executeSpy = vi.spyOn(aug, 'execute')
brain.augmentations.register(aug)
await brain.init()
// Trigger the augmentation
await brain.add('test data')
// Verify it was called
expect(executeSpy).toHaveBeenCalledWith(
'add',
expect.objectContaining({ content: 'test data' }),
expect.any(Object)
)
})
})
```
---
## Best Practices
### 1. Use Proper Timing
- `before`: Validation, modification, rate limiting
- `after`: Logging, metrics, side effects
- `both`: Timing, tracing, wrapping
### 2. Set Appropriate Priority
```typescript
// Priority guidelines
100: Critical (auth, rate limiting)
50: Important (validation, transformation)
10: Normal (logging, metrics)
1: Optional (debugging, tracing)
```
### 3. Handle Errors Gracefully
```typescript
async execute<T>(operation: string, params: any): Promise<void> {
try {
await this.riskyOperation()
} catch (error) {
// Log but don't break the main operation
console.error(`Augmentation error in ${this.name}:`, error)
// Optionally report to monitoring
this.reportError(error)
}
}
```
### 4. Be Performance Conscious
```typescript
class CachedAugmentation extends BaseAugmentation {
private cache = new Map<string, any>()
async execute<T>(operation: string, params: any): Promise<any> {
const key = this.getCacheKey(params)
// Check cache first
if (this.cache.has(key)) {
return this.cache.get(key)
}
// Expensive operation
const result = await this.expensiveOperation(params)
this.cache.set(key, result)
return result
}
}
```
### 5. Clean Up Resources
```typescript
protected async onShutdown(): Promise<void> {
// Close connections
await this.connection?.close()
// Clear intervals
clearInterval(this.interval)
// Flush buffers
await this.flush()
// Clear caches
this.cache.clear()
}
```
---
## Publishing Your Augmentation (Future)
### Package Structure
```
my-augmentation/
├── src/
│ └── index.ts # Your augmentation
├── dist/ # Built output
├── tests/
│ └── augmentation.test.ts
├── package.json
├── tsconfig.json
└── README.md
```
### package.json
```json
{
"name": "@mycompany/brainy-custom-augmentation",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"keywords": ["brainy-augmentation"],
"peerDependencies": {
"@soulcraft/brainy": ">=2.0.0"
},
"brainy": {
"type": "augmentation",
"class": "CustomAugmentation",
"timing": "after",
"operations": ["add"],
"priority": 10
}
}
```
### Future: Brain Cloud Registry
```bash
# Coming in 2.1+
npm run build
npm test
brainy publish # Publishes to brain-cloud registry
```
---
## FAQ
### Q: Can I modify the operation result?
**A**: Yes, if `timing: 'before'`, return modified params. If `timing: 'after'`, you can see but not modify results.
### Q: Can augmentations communicate?
**A**: Yes, through the context: `context.brain.augmentations.get('other-augmentation')`
### Q: What if my augmentation fails?
**A**: Handle errors internally. Don't break the main operation unless critical.
### Q: Can I use async operations?
**A**: Yes, everything is async-friendly.
### Q: How do I access storage directly?
**A**: Through context: `context.brain.storage` (but prefer using brain methods)
---
## Get Help
- **GitHub**: [github.com/soulcraft/brainy](https://github.com/soulcraft/brainy)
- **Discord**: [discord.gg/brainy](https://discord.gg/brainy)
- **Examples**: See `/examples/augmentations/` in the repo
---
*Start building your augmentation today! The marketplace is coming in 2.1 🚀*

View file

@ -1,669 +0,0 @@
# Augmentation Examples - Import, Store, Export
This guide shows two complete workflows:
1. **Simple Handler** - Just import a new file type
2. **Full Augmentation** - Import + Store + Export (premium-ready)
---
## Workflow 1: Simple Handler (Import Only)
**Use case:** You want to import a new file type (e.g., CAD files) into Brainy's knowledge graph.
### Step 1: Create the Handler
```typescript
// src/handlers/CADHandler.ts
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import type { FormatHandlerOptions, ProcessedData } from '@soulcraft/brainy/augmentations/intelligentImport'
import { parseCAD } from 'cad-parser' // Your parsing library
export class CADHandler extends BaseFormatHandler {
readonly format = 'cad'
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
if (typeof data === 'object' && 'filename' in data) {
const mimeType = this.getMimeType(data)
return this.mimeTypeMatches(mimeType, ['image/vnd.dwg', 'image/vnd.dxf'])
}
return false
}
async process(data: Buffer | string, options: FormatHandlerOptions): Promise<ProcessedData> {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
// Parse CAD file
const cad = await parseCAD(buffer)
// Extract entities for knowledge graph
const entities = []
// Document entity
entities.push({
type: 'CADDocument',
filename: options.filename,
units: cad.units,
bounds: cad.bounds,
version: cad.version
})
// Layer entities
for (const layer of cad.layers) {
entities.push({
type: 'CADLayer',
name: layer.name,
color: layer.color,
visible: layer.visible
})
}
// Object entities
for (const obj of cad.objects) {
entities.push({
type: 'CADObject',
objectType: obj.type,
layer: obj.layer,
geometry: obj.geometry
})
}
return {
format: 'cad',
data: entities,
metadata: {
rowCount: entities.length,
fields: ['type', 'name', 'geometry'],
processingTime: Date.now() - startTime,
layerCount: cad.layers.length,
objectCount: cad.objects.length
},
filename: options.filename
}
}
}
```
### Step 2: Register the Handler
```typescript
// src/index.ts
import { globalHandlerRegistry } from '@soulcraft/brainy/augmentations/intelligentImport'
import { CADHandler } from './handlers/CADHandler.js'
// Register handler globally
globalHandlerRegistry.registerHandler({
name: 'cad',
mimeTypes: ['image/vnd.dwg', 'image/vnd.dxf'],
extensions: ['.dwg', '.dxf', '.dwf'],
loader: async () => new CADHandler()
})
```
### Step 3: Use It
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// Import CAD file - automatically routed to CADHandler
const result = await brain.import({
type: 'file',
data: cadFileBuffer,
filename: 'floor-plan.dwg'
})
console.log(`Imported ${result.entities.length} CAD entities`)
// Query the imported data
const layers = await brain.find({ type: 'CADLayer' })
const objects = await brain.find({ type: 'CADObject', layer: 'WALLS' })
```
**That's it!** Simple handlers just import data. Brainy handles storage automatically.
---
## Workflow 2: Full Augmentation (Import + Store + Export)
**Use case:** You want a complete solution that imports project files, stores them with special logic, and exports results (e.g., React project analyzer).
### Step 1: Create the Augmentation
```typescript
// @yourcompany/brainy-react-analyzer
import { BaseAugmentation, type AugmentationContext } from '@soulcraft/brainy'
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import type { ProcessedData } from '@soulcraft/brainy/augmentations/intelligentImport'
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import * as t from '@babel/types'
/**
* React Project Analyzer Augmentation
*
* Features:
* - Import: Parse React components, extract props, hooks, imports
* - Store: Create relationships between components
* - Export: Generate component diagram, dependency graph
*/
export class ReactAnalyzerAugmentation extends BaseAugmentation {
readonly name = 'react-analyzer'
readonly timing = 'before' as const
readonly operations = ['import', 'export'] as any[]
readonly priority = 75
private handler: ReactComponentHandler
constructor(config = {}) {
super(config)
this.handler = new ReactComponentHandler()
}
async execute<T = any>(
operation: string,
params: any,
next: () => Promise<T>
): Promise<T> {
// IMPORT: Parse React files
if (operation === 'import' && this.isReactFile(params)) {
return this.handleImport(params, next)
}
// EXPORT: Generate diagrams/reports
if (operation === 'export' && params.format === 'react-diagram') {
return this.handleExport(params, next)
}
return next()
}
private isReactFile(params: any): boolean {
const filename = params.filename || ''
return (
(filename.endsWith('.tsx') || filename.endsWith('.jsx')) &&
params.data?.includes('React')
)
}
private async handleImport<T>(params: any, next: () => Promise<T>): Promise<T> {
// Parse React component
const processed = await this.handler.process(params.data, params.options)
// Enrich with relationships
params.data = processed.data
params.metadata = {
...params.metadata,
reactAnalysis: processed.metadata
}
// Continue to next augmentation/storage
const result = await next()
// Post-process: Create component relationships
await this.createComponentRelationships(processed, result)
return result
}
private async createComponentRelationships(
processed: ProcessedData,
result: any
): Promise<void> {
const brain = this.getBrain()
if (!brain) return
// Find the component entity that was created
const component = processed.data.find(d => d.type === 'ReactComponent')
if (!component) return
// Create relationships for imports
for (const imp of component.imports || []) {
// Find or create imported component
const imported = await brain.findOne({
type: 'ReactComponent',
name: imp.name
})
if (imported) {
// Create "Imports" relationship
await brain.createRelation({
source: result.entities[0].id,
verb: 'Imports',
target: imported.id,
metadata: {
importPath: imp.path,
importType: imp.type
}
})
}
}
// Create relationships for prop types
for (const prop of component.props || []) {
if (prop.typeRef) {
const typeEntity = await brain.findOne({
type: 'TypeDefinition',
name: prop.typeRef
})
if (typeEntity) {
await brain.createRelation({
source: result.entities[0].id,
verb: 'UsesPropType',
target: typeEntity.id
})
}
}
}
}
private async handleExport<T>(params: any, next: () => Promise<T>): Promise<T> {
const brain = this.getBrain()
if (!brain) return next()
// Query all React components
const components = await brain.find({ type: 'ReactComponent' })
// Build dependency graph
const graph = await this.buildDependencyGraph(components)
// Generate diagram
const diagram = this.generateMermaidDiagram(graph)
return {
format: 'react-diagram',
diagram,
components: components.length,
dependencies: graph.edges.length
} as T
}
private async buildDependencyGraph(components: any[]): Promise<any> {
const brain = this.getBrain()
const nodes = components.map(c => ({
id: c.id,
name: c.name,
props: c.props
}))
const edges = []
for (const component of components) {
const imports = await brain.getRelated(component.id, 'Imports')
for (const imp of imports) {
edges.push({
from: component.id,
to: imp.id,
type: 'imports'
})
}
}
return { nodes, edges }
}
private generateMermaidDiagram(graph: any): string {
let mermaid = 'graph TD\n'
for (const node of graph.nodes) {
mermaid += ` ${node.id}[${node.name}]\n`
}
for (const edge of graph.edges) {
mermaid += ` ${edge.from} --> ${edge.to}\n`
}
return mermaid
}
}
/**
* React Component Handler
*/
class ReactComponentHandler extends BaseFormatHandler {
readonly format = 'react'
canHandle(data: any): boolean {
if (typeof data === 'object' && 'filename' in data) {
return data.filename?.match(/\.(tsx|jsx)$/) !== null
}
return false
}
async process(data: Buffer | string, options: any): Promise<ProcessedData> {
const code = Buffer.isBuffer(data) ? data.toString('utf-8') : data
// Parse with Babel
const ast = parse(code, {
sourceType: 'module',
plugins: ['jsx', 'typescript']
})
// Extract component info
const components: any[] = []
const imports: any[] = []
const exports: any[] = []
traverse(ast, {
// Detect function components
FunctionDeclaration(path) {
if (this.isReactComponent(path.node)) {
components.push({
type: 'ReactComponent',
name: path.node.id?.name,
componentType: 'function',
props: this.extractProps(path),
hooks: this.extractHooks(path),
state: this.extractState(path)
})
}
},
// Detect class components
ClassDeclaration(path) {
if (this.isReactClassComponent(path.node)) {
components.push({
type: 'ReactComponent',
name: path.node.id.name,
componentType: 'class',
props: this.extractClassProps(path),
state: this.extractClassState(path),
lifecycle: this.extractLifecycleMethods(path)
})
}
},
// Extract imports
ImportDeclaration(path) {
imports.push({
type: 'Import',
from: path.node.source.value,
imports: path.node.specifiers.map(s => ({
name: s.local.name,
imported: t.isImportSpecifier(s) ? s.imported.name : null
}))
})
},
// Extract exports
ExportNamedDeclaration(path) {
exports.push({
type: 'Export',
name: path.node.declaration?.id?.name
})
}
})
// Enrich components with import info
for (const component of components) {
component.imports = imports
component.exports = exports.find(e => e.name === component.name)
}
return {
format: 'react',
data: components,
metadata: {
rowCount: components.length,
fields: ['type', 'name', 'props', 'hooks'],
processingTime: Date.now() - startTime,
componentCount: components.length,
importCount: imports.length,
exportCount: exports.length
},
filename: options.filename
}
}
private isReactComponent(node: any): boolean {
// Check if function returns JSX
return node.body?.body?.some(stmt =>
t.isReturnStatement(stmt) && this.isJSX(stmt.argument)
)
}
private isJSX(node: any): boolean {
return t.isJSXElement(node) || t.isJSXFragment(node)
}
private extractProps(path: any): any[] {
const params = path.node.params
if (params.length === 0) return []
const propsParam = params[0]
if (t.isObjectPattern(propsParam)) {
return propsParam.properties.map(p => ({
name: p.key.name,
type: p.typeAnnotation?.typeAnnotation?.type
}))
}
return []
}
private extractHooks(path: any): string[] {
const hooks: string[] = []
path.traverse({
CallExpression(hookPath) {
const callee = hookPath.node.callee
if (t.isIdentifier(callee) && callee.name.startsWith('use')) {
hooks.push(callee.name)
}
}
})
return hooks
}
private extractState(path: any): any[] {
const stateVars: any[] = []
path.traverse({
CallExpression(hookPath) {
if (
t.isIdentifier(hookPath.node.callee) &&
hookPath.node.callee.name === 'useState'
) {
const parent = hookPath.parent
if (t.isVariableDeclarator(parent) && t.isArrayPattern(parent.id)) {
const [stateVar] = parent.id.elements
if (t.isIdentifier(stateVar)) {
stateVars.push({
name: stateVar.name,
initialValue: hookPath.node.arguments[0]
})
}
}
}
}
})
return stateVars
}
}
```
### Step 2: Package as NPM Module
```json
// package.json
{
"name": "@yourcompany/brainy-react-analyzer",
"version": "1.0.0",
"description": "React project analyzer for Brainy",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"keywords": ["brainy", "react", "analyzer", "augmentation"],
"peerDependencies": {
"@soulcraft/brainy": "^5.2.0"
},
"dependencies": {
"@babel/parser": "^7.23.0",
"@babel/traverse": "^7.23.0",
"@babel/types": "^7.23.0"
}
}
```
### Step 3: Use the Augmentation
```typescript
// Install
// npm install @yourcompany/brainy-react-analyzer
import { Brainy } from '@soulcraft/brainy'
import { ReactAnalyzerAugmentation } from '@yourcompany/brainy-react-analyzer'
const brain = new Brainy()
// Add augmentation
brain.addAugmentation(new ReactAnalyzerAugmentation())
await brain.init()
// Import React project
await brain.import({
type: 'directory',
path: '/path/to/react-project/src',
recursive: true
})
// Query components
const components = await brain.find({ type: 'ReactComponent' })
console.log(`Found ${components.length} React components`)
// Find component dependencies
const appComponent = await brain.findOne({ type: 'ReactComponent', name: 'App' })
const imports = await brain.getRelated(appComponent.id, 'Imports')
console.log(`App component imports:`, imports.map(c => c.name))
// Export diagram
const diagram = await brain.export({
format: 'react-diagram'
})
console.log(diagram.diagram) // Mermaid diagram
```
### Step 4: Premium Licensing (Optional)
```typescript
// Add license checking
export class ReactAnalyzerAugmentation extends BaseAugmentation {
private licenseKey?: string
constructor(config: { licenseKey?: string } = {}) {
super(config)
this.licenseKey = config.licenseKey
}
async onInitialize(): Promise<void> {
if (!this.licenseKey) {
throw new Error('React Analyzer requires a license key. Get one at https://yourcompany.com/brainy-react')
}
// Verify license
const valid = await this.verifyLicense(this.licenseKey)
if (!valid) {
throw new Error('Invalid license key')
}
this.log('React Analyzer initialized successfully')
}
private async verifyLicense(key: string): Promise<boolean> {
// Check with your license server
const response = await fetch('https://api.yourcompany.com/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key, product: 'brainy-react-analyzer' })
})
const data = await response.json()
return data.valid
}
}
// Usage with license
brain.addAugmentation(new ReactAnalyzerAugmentation({
licenseKey: 'YOUR-LICENSE-KEY'
}))
```
---
## Comparison: Handler vs Augmentation
| Feature | Simple Handler | Full Augmentation |
|---------|---------------|-------------------|
| **Import** | ✅ Yes (automatic) | ✅ Yes (with custom logic) |
| **Storage** | ✅ Automatic (Brainy core) | ✅ Custom logic + relationships |
| **Export** | ❌ No | ✅ Yes (custom formats) |
| **Relationships** | ❌ No | ✅ Yes (create custom relationships) |
| **Premium licensing** | ❌ Difficult | ✅ Easy (augmentation-level) |
| **Custom operations** | ❌ Import only | ✅ Any operation |
| **Complexity** | Low (50-100 lines) | Medium (200-500 lines) |
### When to use Handler:
- Just need to import a new file type
- Don't need custom export
- Don't need special relationships
- Simple use case
### When to use Augmentation:
- Need import + export workflow
- Need custom relationship logic
- Want premium licensing capability
- Complex business logic
- Multiple operations (import + export + query)
---
## More Examples
### Example: Python Project Analyzer
```typescript
class PythonAnalyzerAugmentation extends BaseAugmentation {
// Import Python files, extract classes/functions
// Create relationships between modules
// Export: Dependency diagram, call graph
}
```
### Example: Database Schema Sync
```typescript
class DatabaseSyncAugmentation extends BaseAugmentation {
// Import: Parse SQL schema
// Store: Tables, columns, relationships
// Export: Generate migration scripts
}
```
### Example: API Documentation Generator
```typescript
class APIDocAugmentation extends BaseAugmentation {
// Import: Parse TypeScript types
// Store: Endpoints, parameters, responses
// Export: OpenAPI spec, Markdown docs
}
```
---
## See Also
- [FORMAT_HANDLERS.md](./FORMAT_HANDLERS.md) - Creating format handlers
- [AUGMENTATIONS.md](./AUGMENTATIONS.md) - Augmentation system details
- [ImageHandler source](../../src/augmentations/intelligentImport/handlers/imageHandler.ts) - Handler example
- [IntelligentImportAugmentation source](../../src/augmentations/intelligentImport/IntelligentImportAugmentation.ts) - Augmentation example

View file

@ -1,687 +0,0 @@
# Creating Custom Format Handlers
**Version:** 5.2.0+
Format handlers enable you to import ANY file type into Brainy as structured knowledge graph data. This guide shows how to create custom format handlers for your specific file formats.
---
## Quick Start
```typescript
import { BaseFormatHandler, globalHandlerRegistry } from '@soulcraft/brainy/augmentations/intelligentImport'
import type { FormatHandlerOptions, ProcessedData } from '@soulcraft/brainy/augmentations/intelligentImport'
class MyFormatHandler extends BaseFormatHandler {
readonly format = 'myformat'
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
// Option 1: Check by MIME type
if (typeof data === 'object' && 'filename' in data) {
const mimeType = this.getMimeType(data)
return this.mimeTypeMatches(mimeType, ['application/x-myformat'])
}
// Option 2: Check by magic bytes
if (Buffer.isBuffer(data)) {
return data[0] === 0x4D && data[1] === 0x59 // "MY" magic bytes
}
return false
}
async process(
data: Buffer | string,
options: FormatHandlerOptions
): Promise<ProcessedData> {
// Convert to Buffer
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
// Parse your format
const parsed = this.parseMyFormat(buffer)
// Return structured data
return {
format: 'myformat',
data: [
{
type: 'MyEntity',
name: parsed.name,
metadata: parsed.metadata
}
],
metadata: {
rowCount: 1,
fields: ['type', 'name', 'metadata'],
processingTime: Date.now() - startTime
}
}
}
private parseMyFormat(buffer: Buffer): any {
// Your parsing logic here
return { name: 'example', metadata: {} }
}
}
// Register globally
globalHandlerRegistry.registerHandler({
name: 'myformat',
mimeTypes: ['application/x-myformat'],
extensions: ['.myf', '.myfmt'],
loader: async () => new MyFormatHandler()
})
```
Now Brainy automatically handles your format:
```typescript
await brain.import({
type: 'file',
data: myFormatBuffer,
filename: 'data.myf'
})
// Automatically routes to MyFormatHandler!
```
---
## BaseFormatHandler
All format handlers should extend `BaseFormatHandler`, which provides:
### MIME Type Detection
```typescript
protected getMimeType(data: Buffer | string | { filename?: string }): string
```
Detects MIME type from filename or buffer. Uses Brainy's comprehensive MIME detection (2000+ types).
**Example:**
```typescript
const mimeType = this.getMimeType({ filename: 'data.dwg' })
// Returns: 'image/vnd.dwg'
```
### MIME Type Matching
```typescript
protected mimeTypeMatches(mimeType: string, patterns: string[]): boolean
```
Checks if MIME type matches patterns. Supports wildcards (`image/*`).
**Example:**
```typescript
if (this.mimeTypeMatches(mimeType, ['image/*', 'video/*'])) {
// Handle all images and videos
}
```
### Extension Detection
```typescript
protected detectExtension(data: string | Buffer | { filename?: string; ext?: string }): string | null
```
Extracts file extension for fallback detection.
---
## canHandle() Method
The `canHandle()` method determines if your handler can process the given data.
### Strategy 1: MIME Type Detection (Recommended)
```typescript
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
if (typeof data === 'object' && 'filename' in data) {
const mimeType = this.getMimeType(data)
return this.mimeTypeMatches(mimeType, [
'application/x-myformat',
'application/myformat'
])
}
return false
}
```
**Pros:** Automatic, comprehensive, works with 2000+ types
**Cons:** Requires filename
### Strategy 2: Magic Byte Detection
```typescript
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
if (Buffer.isBuffer(data)) {
// Check magic bytes
return (
data[0] === 0x50 && // 'P'
data[1] === 0x4B && // 'K'
data[2] === 0x03 &&
data[3] === 0x04 // ZIP signature
)
}
return false
}
```
**Pros:** Works without filename, robust
**Cons:** Requires knowledge of format structure
### Strategy 3: Combined Approach
```typescript
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
// Try MIME type first
if (typeof data === 'object' && 'filename' in data) {
const mimeType = this.getMimeType(data)
if (this.mimeTypeMatches(mimeType, ['application/x-myformat'])) {
return true
}
}
// Fallback to magic bytes
if (Buffer.isBuffer(data)) {
return this.checkMagicBytes(data)
}
return false
}
```
**Pros:** Robust, works in all scenarios
**Cons:** More complex
---
## process() Method
The `process()` method extracts structured data from the file.
### Return Format
```typescript
interface ProcessedData {
/** Format identifier */
format: string
/** Array of extracted entities */
data: Array<Record<string, any>>
/** Metadata about processing */
metadata: {
rowCount: number
fields: string[]
processingTime: number
[key: string]: any
}
/** Original filename (optional) */
filename?: string
}
```
### Example: CAD File Handler
```typescript
async process(data: Buffer | string, options: FormatHandlerOptions): Promise<ProcessedData> {
const startTime = Date.now()
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
// Parse CAD file
const cad = await this.parseCAD(buffer)
// Extract entities
const entities = []
// Main CAD document
entities.push({
type: 'CADDocument',
filename: options.filename,
units: cad.units,
bounds: cad.bounds
})
// Layers
for (const layer of cad.layers) {
entities.push({
type: 'CADLayer',
name: layer.name,
color: layer.color,
visible: layer.visible,
objectCount: layer.objects.length
})
}
// Objects
for (const obj of cad.objects) {
entities.push({
type: 'CADObject',
objectType: obj.type,
layer: obj.layer,
geometry: obj.geometry,
properties: obj.properties
})
}
return {
format: 'cad',
data: entities,
metadata: {
rowCount: entities.length,
fields: ['type', 'name', 'geometry', 'properties'],
processingTime: Date.now() - startTime,
layerCount: cad.layers.length,
objectCount: cad.objects.length,
units: cad.units
},
filename: options.filename
}
}
```
### Best Practices
1. **Always track processing time:**
```typescript
const startTime = Date.now()
// ... processing ...
metadata.processingTime = Date.now() - startTime
```
2. **Include rich metadata:**
```typescript
metadata: {
rowCount: entities.length,
fields: ['type', 'name', ...],
processingTime: 123,
// Format-specific metadata
layerCount: 5,
objectCount: 150,
version: '2.0'
}
```
3. **Handle errors gracefully:**
```typescript
try {
const parsed = this.parse(buffer)
return { format: 'myformat', data: parsed, ... }
} catch (error) {
throw new Error(
`Failed to parse myformat: ${error instanceof Error ? error.message : String(error)}`
)
}
```
4. **Support progress reporting (optional):**
```typescript
if (options.progressHooks?.onCurrentItem) {
options.progressHooks.onCurrentItem(`Processing layer ${i}/${total}`)
}
```
---
## FormatHandlerRegistry
### Global Registry
Use the global registry for application-wide handlers:
```typescript
import { globalHandlerRegistry } from '@soulcraft/brainy/augmentations/intelligentImport'
globalHandlerRegistry.registerHandler({
name: 'myformat',
mimeTypes: ['application/x-myformat'],
extensions: ['.myf'],
loader: async () => new MyFormatHandler()
})
```
### Local Registry
Create a local registry for scoped handlers:
```typescript
import { FormatHandlerRegistry } from '@soulcraft/brainy/augmentations/intelligentImport'
const registry = new FormatHandlerRegistry()
registry.registerHandler({ ... })
```
### Lazy Loading
Handlers are lazy-loaded for performance:
```typescript
globalHandlerRegistry.registerHandler({
name: 'heavy',
mimeTypes: ['application/x-heavy'],
extensions: ['.heavy'],
loader: async () => {
// Only loaded when first needed
const { HeavyHandler } = await import('./HeavyHandler.js')
return new HeavyHandler()
}
})
```
### Getting Handlers
```typescript
// By filename (automatic MIME detection)
const handler = await registry.getHandler('data.myf')
// By MIME type
const handler = await registry.getHandlerByMimeType('application/x-myformat')
// By extension
const handler = await registry.getHandlerByExtension('.myf')
// By name
const handler = await registry.getHandlerByName('myformat')
```
---
## Real-World Examples
### Example 1: Video Metadata Extractor
```typescript
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import ffmpeg from 'fluent-ffmpeg'
class VideoHandler extends BaseFormatHandler {
readonly format = 'video'
canHandle(data) {
const mimeType = this.getMimeType(data)
return this.mimeTypeMatches(mimeType, ['video/*'])
}
async process(data, options) {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
// Extract video metadata with ffmpeg
const metadata = await this.extractVideoMetadata(buffer)
return {
format: 'video',
data: [{
type: 'Video',
duration: metadata.duration,
codec: metadata.codec,
resolution: metadata.resolution,
frameRate: metadata.frameRate,
bitrate: metadata.bitrate,
audioTracks: metadata.audioTracks,
subtitles: metadata.subtitles
}],
metadata: {
rowCount: 1,
fields: ['type', 'duration', 'codec', 'resolution'],
processingTime: metadata.processingTime
}
}
}
private async extractVideoMetadata(buffer: Buffer) {
// Use ffmpeg to extract metadata
return new Promise((resolve, reject) => {
ffmpeg(buffer)
.ffprobe((err, data) => {
if (err) reject(err)
else resolve(this.parseFFmpegOutput(data))
})
})
}
}
```
### Example 2: Git Repository Parser
```typescript
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import { simpleGit } from 'simple-git'
class GitRepoHandler extends BaseFormatHandler {
readonly format = 'git-repo'
canHandle(data) {
// Check for .git directory
if (typeof data === 'object' && 'filename' in data) {
return data.filename?.includes('.git') || false
}
return false
}
async process(data, options) {
const repoPath = options.filename || ''
const git = simpleGit(repoPath)
// Extract commits
const log = await git.log()
const commits = log.all.map(commit => ({
type: 'GitCommit',
hash: commit.hash,
message: commit.message,
author: commit.author_name,
date: commit.date
}))
// Extract branches
const branchSummary = await git.branchLocal()
const branches = Object.keys(branchSummary.branches).map(name => ({
type: 'GitBranch',
name,
current: branchSummary.current === name
}))
return {
format: 'git-repo',
data: [...commits, ...branches],
metadata: {
rowCount: commits.length + branches.length,
fields: ['type', 'hash', 'message', 'author'],
processingTime: Date.now() - startTime,
commitCount: commits.length,
branchCount: branches.length
}
}
}
}
```
### Example 3: Database Schema Importer
```typescript
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import { Client } from 'pg'
class PostgreSQLSchemaHandler extends BaseFormatHandler {
readonly format = 'postgres-schema'
canHandle(data) {
if (typeof data === 'object' && 'filename' in data) {
return data.filename?.endsWith('.sql') || false
}
return false
}
async process(data, options) {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
const sql = buffer.toString('utf-8')
// Parse SQL or connect to database
const schema = await this.parseSchema(sql)
const entities = []
// Tables
for (const table of schema.tables) {
entities.push({
type: 'Table',
name: table.name,
schema: table.schema,
columnCount: table.columns.length
})
// Columns
for (const column of table.columns) {
entities.push({
type: 'Column',
name: column.name,
table: table.name,
dataType: column.dataType,
nullable: column.nullable,
primaryKey: column.primaryKey
})
}
}
// Foreign keys
for (const fk of schema.foreignKeys) {
entities.push({
type: 'ForeignKey',
from: `${fk.fromTable}.${fk.fromColumn}`,
to: `${fk.toTable}.${fk.toColumn}`
})
}
return {
format: 'postgres-schema',
data: entities,
metadata: {
rowCount: entities.length,
fields: ['type', 'name', 'table', 'dataType'],
processingTime: Date.now() - startTime,
tableCount: schema.tables.length,
columnCount: schema.tables.reduce((sum, t) => sum + t.columns.length, 0),
foreignKeyCount: schema.foreignKeys.length
}
}
}
}
```
---
## Creating Premium Augmentations
Package your handler as a premium augmentation:
```typescript
// @yourcompany/brainy-cad-importer
import { BaseAugmentation } from '@soulcraft/brainy'
import { CADHandler } from './CADHandler.js'
export class CADImportAugmentation extends BaseAugmentation {
readonly name = 'cad-import'
readonly timing = 'before'
readonly operations = ['import', 'importFile']
private handler: CADHandler
constructor(config = {}) {
super(config)
this.handler = new CADHandler()
}
async execute(operation, params, next) {
// Check if this is a CAD file
if (this.isCADFile(params)) {
const processed = await this.handler.process(params.data, params.options)
params.data = processed.data
params.metadata = { ...params.metadata, ...processed.metadata }
}
return next()
}
private isCADFile(params: any): boolean {
return this.handler.canHandle(params.data || params)
}
}
// Usage:
// npm install @yourcompany/brainy-cad-importer
// brain.addAugmentation(new CADImportAugmentation())
```
---
## Testing
```typescript
import { describe, it, expect } from 'vitest'
import { MyFormatHandler } from './MyFormatHandler.js'
describe('MyFormatHandler', () => {
let handler: MyFormatHandler
beforeEach(() => {
handler = new MyFormatHandler()
})
describe('canHandle', () => {
it('should handle .myf files', () => {
expect(handler.canHandle({ filename: 'data.myf' })).toBe(true)
})
it('should handle by MIME type', () => {
expect(handler.canHandle({ filename: 'data.myformat' })).toBe(true)
})
it('should reject non-myformat files', () => {
expect(handler.canHandle({ filename: 'data.txt' })).toBe(false)
})
})
describe('process', () => {
it('should extract structured data', async () => {
const testData = Buffer.from('MY format data')
const result = await handler.process(testData)
expect(result.format).toBe('myformat')
expect(result.data).toHaveLength(1)
expect(result.metadata.processingTime).toBeGreaterThan(0)
})
it('should handle errors gracefully', async () => {
const invalidData = Buffer.from('invalid')
await expect(handler.process(invalidData)).rejects.toThrow()
})
})
})
```
---
## Best Practices
1. **Always extend BaseFormatHandler** - provides MIME detection and utilities
2. **Use MIME types for routing** - automatic, comprehensive, maintainable
3. **Lazy load heavy dependencies** - better performance
4. **Extract rich metadata** - make data queryable in knowledge graph
5. **Handle errors gracefully** - fail fast with clear error messages
6. **Test thoroughly** - test canHandle() and process() with real data
7. **Document your format** - explain what data is extracted and how
8. **Follow ProcessedData format** - ensures compatibility with Brainy
---
## See Also
- [ImageHandler source](../../src/augmentations/intelligentImport/handlers/imageHandler.ts) - Reference implementation
- [BaseFormatHandler source](../../src/augmentations/intelligentImport/handlers/base.ts) - Base class
- [FormatHandlerRegistry source](../../src/augmentations/intelligentImport/FormatHandlerRegistry.ts) - Registry implementation
- [Augmentations Guide](./AUGMENTATIONS.md) - Creating augmentations

View file

@ -1,204 +0,0 @@
# Brainy Augmentations
Augmentations are the core extensibility mechanism in Brainy. They allow you to modify, enhance, and extend Brainy's behavior without changing the core code.
## Core Principle: One Interface, Infinite Possibilities
Every augmentation implements the same simple `BrainyAugmentation` interface:
```typescript
interface BrainyAugmentation {
name: string
timing: 'before' | 'after' | 'around' | 'replace'
operations: string[]
priority: number
initialize(context): Promise<void>
execute(operation, params, next): Promise<any>
}
```
This single interface can handle EVERYTHING - from adding AI capabilities to exposing APIs to replacing storage backends.
## Available Augmentations
### 🧠 Data Processing
Augmentations that enhance how data is processed and stored.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **NeuralImportAugmentation** | AI-powered entity and relationship extraction | `before` | ✅ Production |
| **EntityRegistryAugmentation** | High-performance entity deduplication | `before` | ✅ Production |
| **BatchProcessingAugmentation** | Optimizes bulk operations | `around` | ✅ Production |
| **IntelligentVerbScoringAugmentation** | Learns relationship importance over time | `after` | ✅ Production |
### 🔌 External Connections (Synapses)
Connect Brainy to external services and data sources.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **NotionSynapse** | Sync with Notion databases | `after` | 📝 Example |
| **SalesforceSynapse** | Connect to Salesforce CRM | `after` | 📝 Example |
| **SlackSynapse** | Import Slack conversations | `after` | 📝 Example |
| **GoogleDriveSynapse** | Sync Google Drive documents | `after` | 📝 Example |
### 🌐 API Exposure
Expose Brainy through various protocols.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **APIServerAugmentation** | REST, WebSocket, and MCP server | `after` | ✅ Production |
| **GraphQLAugmentation** | GraphQL API endpoint | `after` | 🚧 Planned |
| **gRPCAugmentation** | gRPC service | `after` | 🚧 Planned |
### 💾 Storage Backends
Replace or enhance the storage layer.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **RedisAugmentation** | Redis caching layer | `around` | 📝 Example |
| **PostgresAugmentation** | PostgreSQL persistence | `replace` | 📝 Example |
> Brainy 8.0 ships `filesystem` and `memory` adapters out of the box. For off-site backup of the filesystem artifact, snapshot `rootDirectory` from your scheduler (`gsutil rsync`, `aws s3 sync`, `rclone`, `tar`).
### 🔄 Real-time & Sync
Handle real-time updates and synchronization.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **WebSocketConduitAugmentation** | WebSocket client connections | `after` | ⚠️ Legacy |
| **ServerSearchAugmentation** | Connect to remote Brainy servers | `after` | ⚠️ Legacy |
| **TeamCoordinationAugmentation** | Multi-agent synchronization | `after` | 📝 Example |
### 🛡️ Infrastructure
Core infrastructure and reliability features.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **RequestDeduplicatorAugmentation** | Prevent duplicate concurrent requests | `before` | ✅ Production |
| **TransactionAugmentation** | ACID transaction support | `around` | 🚧 Planned |
| **CacheAugmentation** | Multi-level caching | `around` | ✅ Production |
### 📊 Monitoring & Analytics
Track and analyze Brainy's behavior.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **MetricsAugmentation** | Prometheus metrics | `after` | 📝 Example |
| **LoggingAugmentation** | Structured logging | `after` | 📝 Example |
| **TracingAugmentation** | Distributed tracing | `around` | 🚧 Planned |
### 🤖 AI & Chat
AI-powered interfaces and chat capabilities.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **ChatInterfaceAugmentation** | Natural language interface | `before` | 📝 Example |
| **MCPAgentMemoryAugmentation** | AI agent memory via MCP | `after` | 📝 Example |
| **LLMQueryAugmentation** | LLM-enhanced queries | `before` | 📝 Example |
### 📈 Visualization
Visual representations of data.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **GraphVisualizationAugmentation** | Real-time graph visualization | `after` | 📝 Example |
| **DashboardAugmentation** | Web-based dashboard | `after` | 🚧 Planned |
## Status Legend
- ✅ **Production**: Fully implemented and tested
- 📝 **Example**: Example implementation available
- 🚧 **Planned**: On the roadmap
- ⚠️ **Legacy**: Being replaced by newer augmentations
## Using Augmentations
### Zero-Config Approach
```typescript
const brain = new Brainy()
// Just register augmentations - they work automatically!
brain.augmentations.register(new EntityRegistryAugmentation())
brain.augmentations.register(new APIServerAugmentation())
await brain.init()
```
### With Configuration
```typescript
const brain = new Brainy()
brain.augmentations.register(
new APIServerAugmentation({
port: 8080,
auth: { required: true }
})
)
await brain.init()
```
## Creating Custom Augmentations
See [Creating Custom Augmentations](./creating-augmentations.md) for a complete guide.
Quick example:
```typescript
class MyAugmentation extends BaseAugmentation {
readonly name = 'my-augmentation'
readonly timing = 'after'
readonly operations = ['add', 'search']
readonly priority = 50
async execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T> {
console.log(`Before ${operation}`)
const result = await next()
console.log(`After ${operation}`)
return result
}
}
```
## Augmentation Timing
### `before`
Executes before the main operation. Used for:
- Input validation
- Data transformation
- Authentication checks
### `after`
Executes after the main operation. Used for:
- Broadcasting updates
- Syncing to external services
- Logging and metrics
### `around`
Wraps the main operation. Used for:
- Transactions
- Caching
- Error handling
### `replace`
Completely replaces the main operation. Used for:
- Alternative storage backends
- Mock implementations
- Proxy operations
## Priority System
Higher numbers execute first:
- **100**: Critical system operations
- **50**: Performance optimizations
- **10**: Enhancement features
- **1**: Optional features
## Related Documentation
- [API Server Augmentation](./api-server.md) - Complete API server documentation
- [Creating Augmentations](./creating-augmentations.md) - How to build your own
- [Augmentation Examples](../AUGMENTATION-EXAMPLES.md) - Real-world examples
- [Architecture Overview](../COMPLETE-ARCHITECTURE-VISION.md) - System architecture

View file

@ -1,429 +0,0 @@
# API Server Augmentation
## Overview
The `APIServerAugmentation` is a powerful augmentation that exposes your Brainy instance through REST, WebSocket, and MCP (Model Context Protocol) APIs. It transforms Brainy into a full-featured API server with zero configuration required.
## Features
### 🌐 REST API
Complete CRUD operations and advanced queries through HTTP endpoints.
### 🔌 WebSocket Server
Real-time bidirectional communication with automatic operation broadcasting.
### 🧠 MCP Integration
Built-in Model Context Protocol support for AI agent communication.
### 📊 Operation Broadcasting
Automatically broadcasts all Brainy operations to subscribed WebSocket clients.
### 🔒 Optional Security
Built-in authentication and rate limiting when needed.
## Installation
The APIServerAugmentation is included in Brainy core. No additional installation required.
**Bun** (recommended): No additional dependencies needed - use `Bun.serve()` directly.
**Node.js**: Install optional Express dependencies:
```bash
npm install express cors ws
```
## Zero-Config Usage
```typescript
import { Brainy } from 'brainy'
import { APIServerAugmentation } from 'brainy/augmentations'
const brain = new Brainy()
// Register the API server augmentation
brain.augmentations.register(new APIServerAugmentation())
await brain.init()
// Server is now running at http://localhost:3000
console.log('API Server ready!')
console.log('REST: http://localhost:3000/api/*')
console.log('WebSocket: ws://localhost:3000/ws')
console.log('MCP: http://localhost:3000/api/mcp')
```
## Configuration Options
While zero-config works great, you can customize the server:
```typescript
const apiServer = new APIServerAugmentation({
enabled: true, // Enable/disable the server
port: 3000, // HTTP port
host: '0.0.0.0', // Bind address
cors: {
origin: '*', // CORS allowed origins
credentials: true // Allow credentials
},
auth: {
required: false, // Require authentication
apiKeys: [], // Valid API keys
bearerTokens: [] // Valid bearer tokens
},
rateLimit: {
windowMs: 60000, // Rate limit window (ms)
max: 100 // Max requests per window
}
})
```
## REST API Endpoints
### Health Check
```http
GET /health
```
Returns server status and basic metrics.
### Search
```http
POST /api/search
Content-Type: application/json
{
"query": "search text",
"limit": 10,
"options": {}
}
```
### Add Data
```http
POST /api/add
Content-Type: application/json
{
"content": "data to add",
"metadata": {
"key": "value"
}
}
```
### Get by ID
```http
GET /api/get/:id
```
### Delete
```http
DELETE /api/delete/:id
```
### Create Relationship
```http
POST /api/relate
Content-Type: application/json
{
"source": "id1",
"target": "id2",
"verb": "relates_to",
"metadata": {}
}
```
### Complex Queries
```http
POST /api/find
Content-Type: application/json
{
"where": { "type": "document" },
"like": "machine learning",
"limit": 10
}
```
### Clustering
```http
POST /api/cluster
Content-Type: application/json
{
"algorithm": "kmeans",
"options": {
"k": 5
}
}
```
### Statistics
```http
GET /api/stats
```
### Operation History
```http
GET /api/history
```
## WebSocket API
### Connection
```javascript
const ws = new WebSocket('ws://localhost:3000/ws')
ws.onopen = () => {
console.log('Connected to Brainy WebSocket')
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
console.log('Received:', msg)
}
```
### Subscribe to Operations
```javascript
ws.send(JSON.stringify({
type: 'subscribe',
operations: ['all'] // or specific: ['add', 'search', 'delete']
}))
```
### Search via WebSocket
```javascript
ws.send(JSON.stringify({
type: 'search',
query: 'your search',
limit: 10,
requestId: 'unique-id'
}))
```
### Add Data via WebSocket
```javascript
ws.send(JSON.stringify({
type: 'add',
content: 'data to add',
metadata: {},
requestId: 'unique-id'
}))
```
### Operation Broadcasts
When subscribed, you'll receive real-time updates:
```javascript
{
"type": "operation",
"operation": "add",
"params": { /* sanitized parameters */ },
"timestamp": 1234567890,
"duration": 15
}
```
## MCP (Model Context Protocol)
The MCP endpoint allows AI agents to interact with Brainy:
```http
POST /api/mcp
Content-Type: application/json
{
"method": "search",
"params": {
"query": "find documents about AI"
}
}
```
## Authentication
When authentication is enabled:
### API Key
```http
GET /api/stats
X-API-Key: your-api-key
```
### Bearer Token
```http
GET /api/stats
Authorization: Bearer your-token
```
## Environment Support
### Bun ✅ (Recommended)
Native performance with Bun.serve() - no Express required. Works with `bun --compile` for single-binary deployment.
```typescript
// Simple Bun server with Brainy
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url)
if (url.pathname === '/api/search') {
const { query } = await req.json()
const results = await brain.search(query)
return Response.json(results)
}
return new Response('Not Found', { status: 404 })
}
})
```
### Node.js ✅
Full support with Express, WebSocket, and all features.
### Deno 🚧
Planned support using Deno.serve() or oak framework.
### Browser/Service Worker 🚧
Planned support for intercepting fetch() calls locally.
## How It Works
The APIServerAugmentation hooks into Brainy's augmentation pipeline:
1. **Timing**: Executes `after` operations complete
2. **Operations**: Monitors `all` operations
3. **Broadcasting**: Sends operation details to subscribed clients
4. **History**: Maintains operation history (last 1000 operations)
## Example: Multi-Client Sync
```typescript
// Server
const brain = new Brainy()
brain.augmentations.register(new APIServerAugmentation())
await brain.init()
// Client 1 - WebSocket subscriber
const ws1 = new WebSocket('ws://localhost:3000/ws')
ws1.onopen = () => {
ws1.send(JSON.stringify({
type: 'subscribe',
operations: ['add', 'delete']
}))
}
ws1.onmessage = (e) => {
console.log('Client 1 received update:', JSON.parse(e.data))
}
// Client 2 - REST API user
fetch('http://localhost:3000/api/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: 'New data',
metadata: { source: 'client2' }
})
})
// Client 1 automatically receives notification!
```
## Performance Considerations
- **Operation History**: Limited to last 1000 operations
- **WebSocket Heartbeat**: Every 30 seconds
- **Client Timeout**: 60 seconds of inactivity
- **Parameter Sanitization**: Sensitive fields removed, large content truncated
- **Rate Limiting**: In-memory tracking (use Redis in production)
## Security Notes
1. **Default Configuration**: No auth, open CORS - suitable for development
2. **Production**: Enable auth, configure CORS, use HTTPS
3. **Sensitive Data**: Parameters are sanitized before broadcasting
4. **Rate Limiting**: Basic in-memory implementation included
## Comparison with Previous Implementations
The APIServerAugmentation unifies and replaces:
- `BrainyMCPBroadcast` - Node-specific WebSocket/HTTP server
- `WebSocketConduitAugmentation` - WebSocket client functionality
- `ServerSearchAugmentations` - Remote Brainy connections
Benefits of the unified approach:
- Single augmentation for all API needs
- Consistent interface across protocols
- Automatic operation broadcasting
- Environment-aware implementation
- Zero-configuration philosophy
## Advanced Usage
### Custom Operation Filtering
```typescript
class FilteredAPIServer extends APIServerAugmentation {
shouldExecute(operation: string, params: any): boolean {
// Don't broadcast sensitive operations
if (operation === 'delete' && params.sensitive) {
return false
}
return true
}
}
```
### Integration with Other Augmentations
```typescript
const brain = new Brainy()
// Stack augmentations for complete system
brain.augmentations.register(new EntityRegistryAugmentation()) // Dedup
brain.augmentations.register(new APIServerAugmentation()) // API
await brain.init()
// All augmentations work together seamlessly!
```
## Troubleshooting
### Server won't start
- Check if port is already in use
- Verify Node.js dependencies are installed: `npm install express cors ws`
- Check console for error messages
### WebSocket connections drop
- Ensure heartbeat responses are handled
- Check for proxy/firewall issues
- Verify CORS configuration
### Authentication not working
- Ensure `auth.required` is set to `true`
- Verify API keys or bearer tokens are correctly configured
- Check request headers are properly formatted
## Future Enhancements
- [ ] Deno server implementation
- [ ] Service Worker implementation
- [ ] GraphQL endpoint
- [ ] gRPC support
- [ ] Built-in SSL/TLS
- [ ] Redis-based rate limiting
- [ ] Prometheus metrics endpoint
- [ ] OpenAPI/Swagger documentation
## Related Documentation
- [Augmentation System Overview](../AUGMENTATION-SYSTEM.md)
- [BrainyAugmentation Interface](./brainy-augmentation.md)
- [MCP Integration](../mcp/README.md)
- [Zero-Config Philosophy](../ZERO-CONFIG.md)

View file

@ -41,7 +41,7 @@ by a dedicated test in `tests/integration/db-mvcc.test.ts`.
A **monotonic generation counter** is the store's logical clock: A **monotonic generation counter** is the store's logical clock:
- It advances **once per committed `transact()` batch** and once per - It advances **once per committed `transact()` batch** and once per
single-operation write (`add`/`update`/`delete`/`relate`/…). single-operation write (`add`/`update`/`remove`/`relate`/…).
- `brain.generation()` reads it; it is persisted in the data directory and - `brain.generation()` reads it; it is persisted in the data directory and
**never reissued** — not across restarts, and not across `restore()` **never reissued** — not across restarts, and not across `restore()`
(the counter is floored at its pre-restore value). (the counter is floored at its pre-restore value).
@ -175,7 +175,7 @@ identical on both paths.
### History granularity — the honest limit ### History granularity — the honest limit
Generation *records* are written per `transact()` batch only. Generation *records* are written per `transact()` batch only.
Single-operation writes (`add`/`update`/`delete`/`relate`/… outside Single-operation writes (`add`/`update`/`remove`/`relate`/… outside
`transact()`) advance the generation counter — so watermarks and CAS stay `transact()`) advance the generation counter — so watermarks and CAS stay
sound — but do **not** stage before-images: they remain visible through sound — but do **not** stage before-images: they remain visible through
earlier pins and are not reported by `db.since()`. Code that needs pinned earlier pins and are not reported by `db.since()`. Code that needs pinned
@ -284,7 +284,7 @@ enforce the contract:
`update({ metadata: { confidence: 0.9 } })` therefore behaves exactly `update({ metadata: { confidence: 0.9 } })` therefore behaves exactly
like `update({ confidence: 0.9 })`. like `update({ confidence: 0.9 })`.
3. **Read time** — every read path (`get`, `find`, `search`, 3. **Read time** — every read path (`get`, `find`, `search`,
`getRelations`, batch reads, and historical `asOf()` materialization) `related`, batch reads, and historical `asOf()` materialization)
surfaces reserved fields **only at top level**: `entity.metadata` and surfaces reserved fields **only at top level**: `entity.metadata` and
`relation.metadata` contain only your custom fields, always. `relation.metadata` contain only your custom fields, always.

View file

@ -152,7 +152,7 @@ Optimized bulk processing:
// Parallel processing with automatic batching // Parallel processing with automatic batching
await brain.addMany(items) // <10ms per item await brain.addMany(items) // <10ms per item
await brain.updateMany(updates) await brain.updateMany(updates)
await brain.deleteMany(filters) await brain.removeMany(filters)
``` ```
### Request Deduplication ✅ ### Request Deduplication ✅

View file

@ -348,7 +348,7 @@ export default function RootLayout({ children }) {
import { Brainy } from '@soulcraft/brainy' import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy({ const brain = new Brainy({
storage: { type: 'filesystem', path: './data' } storage: { type: 'filesystem', rootDirectory: './data' }
}) })
await brain.init() await brain.init()
@ -536,12 +536,12 @@ import { Brainy } from '@soulcraft/brainy'
export async function generateStaticProps() { export async function generateStaticProps() {
const brain = new Brainy({ const brain = new Brainy({
storage: { type: 'filesystem', path: './content' } storage: { type: 'filesystem', rootDirectory: './content' }
}) })
await brain.init() await brain.init()
// Build search index // Build search index (paginate with { limit, offset } for larger stores)
const allContent = await brain.export() const allContent = await brain.find({ limit: 1000 })
return { return {
props: { searchIndex: allContent } props: { searchIndex: allContent }

View file

@ -49,28 +49,23 @@ await brain.import(csv, { format: 'csv' })
### 📊 Import Excel - Multi-Sheet Support ### 📊 Import Excel - Multi-Sheet Support
```javascript ```javascript
// Import entire Excel workbook // Import entire Excel workbook — every sheet is processed automatically
await brain.import('sales-report.xlsx') await brain.import('sales-report.xlsx')
// ✨ Processes all sheets, preserves structure, infers types! // ✨ Processes all sheets, preserves structure, infers types!
// Or specific sheets only // Mirror the workbook into the VFS, grouped by sheet
await brain.import('data.xlsx', { await brain.import('data.xlsx', {
excelSheets: ['Customers', 'Orders'] vfsPath: '/imports/data',
groupBy: 'sheet'
}) })
// ✨ Multi-sheet data becomes interconnected entities! // ✨ Multi-sheet data becomes interconnected entities!
``` ```
### 📑 Import PDF - Text & Tables ### 📑 Import PDF - Text & Tables
```javascript ```javascript
// Import PDF documents // Import PDF documents — text and tables are extracted automatically
await brain.import('research-paper.pdf') await brain.import('research-paper.pdf')
// ✨ Extracts text, detects tables, preserves metadata! // ✨ Extracts text, detects tables, preserves metadata!
// With table extraction
await brain.import('report.pdf', {
pdfExtractTables: true
})
// ✨ Converts PDF tables to structured data automatically!
``` ```
### 📝 Import YAML - File or String ### 📝 Import YAML - File or String

View file

@ -1173,21 +1173,22 @@ await this.graphIndex.addEdge(
``` ```
**Benefits**: **Benefits**:
- **O(1) relationship lookups**: `getRelations(entityId)` is instant - **O(1) relationship lookups**: `related(entityId)` is instant
- **Bidirectional traversal**: Find incoming and outgoing edges - **Bidirectional traversal**: Find incoming and outgoing edges
- **Type filtering**: Get only `CreatedBy` relationships - **Type filtering**: Get only `CreatedBy` relationships
- **Global statistics**: Count relationships by type - **Global statistics**: Count relationships by type
**Query Examples**: **Query Examples**:
```typescript ```typescript
// What did Mona Lisa create? // What did Mona Lisa create? (outgoing edges)
const outgoing = await brain.getRelations('ent_mona_lisa_...', { direction: 'outgoing' }) const outgoing = await brain.related({ from: 'ent_mona_lisa_...' })
// What created Mona Lisa? // What created Mona Lisa? (incoming edges)
const incoming = await brain.getRelations('ent_mona_lisa_...', { direction: 'incoming' }) const incoming = await brain.related({ to: 'ent_mona_lisa_...' })
// Get only CreatedBy relationships // Get only CreatedBy relationships
const createdBy = await brain.getRelations('ent_mona_lisa_...', { const createdBy = await brain.related({
from: 'ent_mona_lisa_...',
type: VerbType.CreatedBy type: VerbType.CreatedBy
}) })
``` ```

View file

@ -351,7 +351,7 @@ const result = await brain.import(file)
const products = await brain.find({ type: 'Product' }) const products = await brain.find({ type: 'Product' })
// Get entity relationships // Get entity relationships
const relations = await brain.getRelations(products[0].id) const relations = await brain.related(products[0].id)
// Search VFS // Search VFS
const vfsFiles = await brain.vfs().find(result.vfs.rootPath + '/**/*.json') const vfsFiles = await brain.vfs().find(result.vfs.rootPath + '/**/*.json')

View file

@ -134,7 +134,7 @@ console.log(health.overall)
await reader.close() await reader.close()
``` ```
Every mutation method (`add`, `update`, `delete`, `relate`, `transact`, Every mutation method (`add`, `update`, `remove`, `relate`, `transact`,
`restore`, ...) throws on a read-only instance with a clear message. `restore`, ...) throws on a read-only instance with a clear message.
## Backups ## Backups

View file

@ -5,10 +5,10 @@ public: true
category: getting-started category: getting-started
template: guide template: guide
order: 1 order: 1
description: Install Brainy with npm, bun, yarn, or pnpm. Works in Node.js 22+, Bun 1.0+, and browser (OPFS). TypeScript included. description: Install Brainy with npm, bun, yarn, or pnpm. Works in Node.js 22+ and Bun 1.0+ (server-only since 8.0). TypeScript included.
next: next:
- getting-started/quick-start - getting-started/quick-start
- concepts/zero-config - guides/storage-adapters
--- ---
# Installation # Installation
@ -53,28 +53,18 @@ npm install @soulcraft/cortex
```typescript ```typescript
import { Brainy } from '@soulcraft/brainy' import { Brainy } from '@soulcraft/brainy'
import { registerCortex } from '@soulcraft/cortex'
registerCortex() // activates native acceleration globally const brain = new Brainy({ plugins: ['@soulcraft/cortex'] })
await brain.init() // native providers registered during init
const brain = new Brainy()
await brain.init()
``` ```
Cortex delivers a **5.2x geometric mean speedup** — see [Brainy vs Cortex](/docs/cortex/comparison) for measured benchmarks. Cortex delivers a **5.2x geometric mean speedup** — see [Brainy vs Cortex](/docs/cortex/comparison) for measured benchmarks.
## Browser (OPFS) ## Server-only since 8.0
Brainy works in the browser using the Origin Private File System: Brainy 8.0 runs on Node.js 22+ and Bun 1.0+. Browser support (OPFS storage,
Web Workers, in-browser WASM embeddings) was removed in 8.0 — the 7.x line
```typescript remains available on npm if you need it.
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy({ storage: 'opfs' })
await brain.init()
```
No server required. Data persists across page refreshes in the browser's private storage.
## TypeScript ## TypeScript
@ -96,5 +86,4 @@ const id = await brain.add({
## Next Steps ## Next Steps
- [Quick Start](/docs/getting-started/quick-start) — build your first knowledge graph in 60 seconds - [Quick Start](/docs/getting-started/quick-start) — build your first knowledge graph in 60 seconds
- [Zero Configuration](/docs/concepts/zero-config) — understand what Brainy auto-detects
- [Storage Adapters](/docs/guides/storage-adapters) — choose the right storage for your deployment - [Storage Adapters](/docs/guides/storage-adapters) — choose the right storage for your deployment

View file

@ -252,7 +252,7 @@ const result = await brain.import('./glossary.xlsx', {
// - "part_of" // - "part_of"
// - "related_to" // - "related_to"
const relations = await brain.getRelations({ limit: 100 }) const relations = await brain.related({ limit: 100 })
const types = new Set(relations.map(r => r.label)) const types = new Set(relations.map(r => r.label))
console.log(types) console.log(types)
// Set { 'capital_of', 'guards', 'located_in', 'related_to' } // Set { 'capital_of', 'guards', 'located_in', 'related_to' }

View file

@ -367,5 +367,5 @@ const brain = new Brainy({
## Next Steps ## Next Steps
- Explore [Triple Intelligence](../architecture/triple-intelligence.md) for combined vector + graph + metadata queries - Explore [Triple Intelligence](../architecture/triple-intelligence.md) for combined vector + graph + metadata queries
- Learn about [Augmentations](../augmentations/README.md) to extend Neural API - Learn about [Plugins](../PLUGINS.md) to extend Brainy with native providers
- See [API Reference](../api/README.md) for complete method documentation - See [API Reference](../api/README.md) for complete method documentation

View file

@ -8,7 +8,7 @@ order: 2
description: "Two adapters cover every deployment: in-memory for tests + ephemeral workloads, filesystem for everything that needs to persist. Both share one on-disk contract, including generational history and snapshots. Cloud backup is operator tooling, not a built-in adapter." description: "Two adapters cover every deployment: in-memory for tests + ephemeral workloads, filesystem for everything that needs to persist. Both share one on-disk contract, including generational history and snapshots. Cloud backup is operator tooling, not a built-in adapter."
next: next:
- guides/plugins - guides/plugins
- concepts/zero-config - concepts/consistency-model
--- ---
# Storage Adapters # Storage Adapters

View file

@ -301,14 +301,14 @@ await brain.relate({
```typescript ```typescript
// Direct reports — fast path filter (column-store hit, not metadata fallback) // Direct reports — fast path filter (column-store hit, not metadata fallback)
const direct = await brain.getRelations({ const direct = await brain.related({
from: ceoId, from: ceoId,
type: VerbType.ReportsTo, type: VerbType.ReportsTo,
subtype: 'direct' subtype: 'direct'
}) })
// Set membership // Set membership
const allReports = await brain.getRelations({ const allReports = await brain.related({
from: ceoId, from: ceoId,
type: VerbType.ReportsTo, type: VerbType.ReportsTo,
subtype: ['direct', 'dotted-line'] subtype: ['direct', 'dotted-line']
@ -552,8 +552,8 @@ Brainy 8.0 ships:
- `brain.relate({ ..., subtype: 'value' })` — write the field - `brain.relate({ ..., subtype: 'value' })` — write the field
- `brain.updateRelation({ id, subtype, type?, weight?, ... })` — change a relationship - `brain.updateRelation({ id, subtype, type?, weight?, ... })` — change a relationship
- `brain.getRelations({ subtype })` — filter (fast path) - `brain.related({ subtype })` — filter (fast path)
- `brain.getRelations({ subtype: ['a', 'b'] })` — set membership - `brain.related({ subtype: ['a', 'b'] })` — set membership
- `brain.find({ connected: { via, subtype, depth } })` — traversal filter - `brain.find({ connected: { via, subtype, depth } })` — traversal filter
- `brain.counts.byRelationshipSubtype(verb, subtype?)` — O(1) counts - `brain.counts.byRelationshipSubtype(verb, subtype?)` — O(1) counts
- `brain.counts.topRelationshipSubtypes(verb, n?)` — top N by count - `brain.counts.topRelationshipSubtypes(verb, n?)` — top N by count

View file

@ -23,7 +23,7 @@ Brainy's transaction system provides **atomic operations** with automatic rollba
- **Atomicity**: All operations succeed or all rollback - **Atomicity**: All operations succeed or all rollback
- **Consistency**: Indexes and storage remain consistent - **Consistency**: Indexes and storage remain consistent
- **Automatic**: Transparently used by all `brain.add()`, `brain.update()`, `brain.delete()`, and `brain.relate()` operations - **Automatic**: Transparently used by all `brain.add()`, `brain.update()`, `brain.remove()`, and `brain.relate()` operations
- **Composable**: `brain.transact()` runs a multi-write batch through the same machinery as exactly one atomic commit - **Composable**: `brain.transact()` runs a multi-write batch through the same machinery as exactly one atomic commit
## Architecture ## Architecture
@ -296,7 +296,7 @@ await brain.relate({
}) })
// Delete person (atomic - deletes entity + relationships) // Delete person (atomic - deletes entity + relationships)
await brain.delete(personId) await brain.remove(personId)
// If delete fails, both entity and relationships remain // If delete fails, both entity and relationships remain
``` ```
@ -363,7 +363,7 @@ for the history-granularity contract.
// ✅ Recommended: Use Brainy's API (transactions automatic) // ✅ Recommended: Use Brainy's API (transactions automatic)
await brain.add({ data, type }) await brain.add({ data, type })
await brain.update({ id, data }) await brain.update({ id, data })
await brain.delete(id) await brain.remove(id)
// ❌ Avoid: Direct storage access bypasses transactions // ❌ Avoid: Direct storage access bypasses transactions
await brain.storage.saveNoun(noun) // No transaction protection await brain.storage.saveNoun(noun) // No transaction protection
@ -565,4 +565,4 @@ Brainy's transaction system provides **production-ready atomic operations** with
- ✅ **Compatible**: Works with all storage adapters and features - ✅ **Compatible**: Works with all storage adapters and features
- ✅ **Coordinated**: Per-entity `ifRev` and whole-store `ifAtGeneration` CAS reject conflicting batches before anything is staged - ✅ **Coordinated**: Per-entity `ifRev` and whole-store `ifAtGeneration` CAS reject conflicting batches before anything is staged
Start using transactions today - they're already built into `brain.add()`, `brain.update()`, `brain.delete()`, and `brain.relate()` — and reach for `brain.transact()` whenever several writes must land together. Start using transactions today - they're already built into `brain.add()`, `brain.update()`, `brain.remove()`, and `brain.relate()` — and reach for `brain.transact()` whenever several writes must land together.

View file

@ -8,7 +8,7 @@
- **[VFS Core](VFS_CORE.md)** - Complete filesystem architecture and API - **[VFS Core](VFS_CORE.md)** - Complete filesystem architecture and API
- **[Semantic VFS](SEMANTIC_VFS.md)** - Multi-dimensional file access (6 semantic dimensions) - **[Semantic VFS](SEMANTIC_VFS.md)** - Multi-dimensional file access (6 semantic dimensions)
- **[Neural Extraction](NEURAL_EXTRACTION.md)** - AI-powered concept and entity extraction - **[Neural Extraction](NEURAL_EXTRACTION.md)** - AI-powered concept and entity extraction
- **[Examples & Scenarios](VFS_EXAMPLES_SCENARIOS.md)** - Real-world use cases and code - **[Common Patterns](COMMON_PATTERNS.md)** - Real-world use cases and code
- **[VFS API Guide](VFS_API_GUIDE.md)** - Complete API reference - **[VFS API Guide](VFS_API_GUIDE.md)** - Complete API reference
## What is Brainy VFS? ## What is Brainy VFS?

View file

@ -400,9 +400,9 @@ Semantic VFS is built on Brainy's Triple Intelligence™:
### Real Implementations, Zero Mocks ### Real Implementations, Zero Mocks
Every component uses **production Brainy APIs**: Every component uses **production Brainy APIs**:
- `brain.find()` - Real metadata queries (brainy.ts:580) - `brain.find()` - Real metadata queries
- `brain.similar()` - Real HNSW search (brainy.ts:680) - `brain.similar()` - Real HNSW search
- `brain.getRelations()` - Real graph traversal (brainy.ts:803) - `brain.related()` - Real graph traversal
- `MetadataIndexManager` - Real B-tree indexes - `MetadataIndexManager` - Real B-tree indexes
- `GraphAdjacencyIndex` - Real graph storage - `GraphAdjacencyIndex` - Real graph storage
- `HNSW Index` - Real vector search - `HNSW Index` - Real vector search

View file

@ -183,7 +183,7 @@ console.log('VFS type:', entity.metadata.vfsType)
### 3. Check Relationships ### 3. Check Relationships
```javascript ```javascript
const parentId = await vfs.resolvePath('/directory') const parentId = await vfs.resolvePath('/directory')
const relations = await brain.getRelations({ const relations = await brain.related({
from: parentId, from: parentId,
type: VerbType.Contains type: VerbType.Contains
}) })
@ -246,7 +246,7 @@ const tree = await vfs.getTreeStructure('/', {
If you encounter issues not covered here: If you encounter issues not covered here:
1. Check the [VFS API Guide](./VFS_API_GUIDE.md) 1. Check the [VFS API Guide](./VFS_API_GUIDE.md)
2. Review [VFS Examples](./VFS_EXAMPLES_SCENARIOS.md) 2. Review [Common Patterns](./COMMON_PATTERNS.md)
3. Look at [test files](../../tests/vfs/) for working examples 3. Look at [test files](../../tests/vfs/) for working examples
4. Report issues at [GitHub Issues](https://github.com/soulcraftlabs/brainy/issues) 4. Report issues at [GitHub Issues](https://github.com/soulcraftlabs/brainy/issues)

View file

@ -1,728 +0,0 @@
# VFS User Functions - Templates and Examples
This document provides template functions that you can implement for domain-specific needs. These functions combine VFS primitives to solve common problems.
## Table of Contents
1. [Code Analysis Functions](#code-analysis-functions)
2. [Export Format Functions](#export-format-functions)
3. [Project Management Functions](#project-management-functions)
4. [Creative Writing Functions](#creative-writing-functions)
5. [Game Development Functions](#game-development-functions)
## Code Analysis Functions
### Get Dependency Graph
```javascript
/**
* Build a dependency graph for JavaScript/TypeScript projects
*/
async function getDependencyGraph(vfs, srcPath) {
const files = await vfs.readdir(srcPath, { recursive: true })
const graph = {}
for (const file of files) {
const filePath = `${srcPath}/${file}`
// Only process JS/TS files
if (file.match(/\.(js|ts|jsx|tsx)$/)) {
const content = await vfs.readFile(filePath)
const text = content.toString()
// Parse imports (basic regex, use proper AST parser for production)
const imports = []
const importRegex = /import\s+.*?\s+from\s+['"](.+?)['"]/g
const requireRegex = /require\(['"](.+?)['"]\)/g
let match
while ((match = importRegex.exec(text)) !== null) {
imports.push(match[1])
}
while ((match = requireRegex.exec(text)) !== null) {
imports.push(match[1])
}
graph[filePath] = imports
}
}
return graph
}
// Use it
const deps = await getDependencyGraph(vfs, '/src')
```
### Find Circular Dependencies
```javascript
/**
* Detect circular dependencies in your code
*/
async function findCircularDependencies(vfs, srcPath) {
const graph = await getDependencyGraph(vfs, srcPath)
const cycles = []
function detectCycle(node, visited = new Set(), stack = []) {
if (stack.includes(node)) {
const cycleStart = stack.indexOf(node)
cycles.push(stack.slice(cycleStart))
return
}
if (visited.has(node)) return
visited.add(node)
stack.push(node)
const dependencies = graph[node] || []
for (const dep of dependencies) {
// Resolve relative imports
const resolvedDep = resolvePath(node, dep)
if (graph[resolvedDep]) {
detectCycle(resolvedDep, visited, [...stack])
}
}
}
Object.keys(graph).forEach(node => detectCycle(node))
return cycles
}
```
### Find Untested Code
```javascript
/**
* Find source files without corresponding test files
*/
async function findUntestedCode(vfs, srcPath, testPath = null) {
testPath = testPath || srcPath.replace('/src', '/tests')
const sourceFiles = await vfs.readdir(srcPath, { recursive: true })
const testFiles = await vfs.readdir(testPath, { recursive: true }).catch(() => [])
const untestedFiles = []
for (const sourceFile of sourceFiles) {
if (!sourceFile.match(/\.(js|ts|jsx|tsx)$/)) continue
// Look for corresponding test file
const baseName = sourceFile.replace(/\.(js|ts|jsx|tsx)$/, '')
const hasTest = testFiles.some(testFile =>
testFile.includes(baseName) &&
testFile.match(/\.(test|spec)\.(js|ts|jsx|tsx)$/)
)
if (!hasTest) {
untestedFiles.push(`${srcPath}/${sourceFile}`)
}
}
return untestedFiles
}
```
### Find Similar Code (Duplicate Detection)
```javascript
/**
* Find potentially duplicate code using similarity scoring
*/
async function findSimilarCode(vfs, filePath, options = {}) {
const threshold = options.threshold || 0.8
const searchPath = options.searchPath || '/'
// Get the reference file content
const referenceContent = await vfs.readFile(filePath)
const referenceText = referenceContent.toString()
// Use VFS's semantic search
const similar = await vfs.findSimilar(filePath, {
limit: 10,
threshold
})
// Additionally, do structural comparison
const results = []
for (const match of similar) {
const matchContent = await vfs.readFile(match.path)
const matchText = matchContent.toString()
// Simple line-based similarity (use better algorithms in production)
const similarity = calculateSimilarity(referenceText, matchText)
if (similarity > threshold) {
results.push({
path: match.path,
similarity,
semanticScore: match.score
})
}
}
return results.sort((a, b) => b.similarity - a.similarity)
}
function calculateSimilarity(text1, text2) {
// Simple Jaccard similarity on lines
const lines1 = new Set(text1.split('\n').map(l => l.trim()).filter(l => l))
const lines2 = new Set(text2.split('\n').map(l => l.trim()).filter(l => l))
const intersection = new Set([...lines1].filter(x => lines2.has(x)))
const union = new Set([...lines1, ...lines2])
return intersection.size / union.size
}
```
## Export Format Functions
### Export to EPUB (for novels)
```javascript
/**
* Export a directory of markdown files to EPUB format
*/
async function exportToEpub(vfs, path, metadata = {}) {
// First get the markdown export
const markdown = await vfs.exportToMarkdown(path)
// You'll need an EPUB library like epub-gen
const Epub = require('epub-gen')
// Convert markdown chapters to EPUB format
const chapters = []
const files = await vfs.readdir(path, { recursive: true })
for (const file of files.sort()) {
if (file.endsWith('.md')) {
const content = await vfs.readFile(`${path}/${file}`)
const title = file.replace('.md', '').replace(/-/g, ' ')
chapters.push({
title: title,
data: content.toString()
})
}
}
const options = {
title: metadata.title || 'My Book',
author: metadata.author || 'Author',
chapters: chapters
}
return new Epub(options)
}
```
### Export to Static Site
```javascript
/**
* Export VFS content to static HTML site
*/
async function exportToStaticSite(vfs, sourcePath, options = {}) {
const json = await vfs.exportToJSON(sourcePath)
const html = []
html.push('<!DOCTYPE html>')
html.push('<html><head>')
html.push(`<title>${options.title || 'Documentation'}</title>`)
html.push('<style>/* Add your styles */</style>')
html.push('</head><body>')
function renderNode(node, name, depth = 0) {
const indent = ' '.repeat(depth)
if (node._meta?.type === 'file') {
html.push(`${indent}<article>`)
html.push(`${indent} <h${Math.min(depth + 2, 6)}>${name}</h${Math.min(depth + 2, 6)}>`)
if (typeof node._content === 'string') {
// Convert markdown to HTML if needed
html.push(`${indent} <pre>${escapeHtml(node._content)}</pre>`)
}
html.push(`${indent}</article>`)
} else if (node._meta?.type === 'directory') {
html.push(`${indent}<section>`)
html.push(`${indent} <h${Math.min(depth + 1, 6)}>${name}</h${Math.min(depth + 1, 6)}>`)
for (const [childName, childNode] of Object.entries(node)) {
if (!childName.startsWith('_')) {
renderNode(childNode, childName, depth + 1)
}
}
html.push(`${indent}</section>`)
}
}
renderNode(json, options.title || 'Root')
html.push('</body></html>')
return html.join('\n')
}
```
### Export to GraphQL Schema
```javascript
/**
* Generate GraphQL schema from VFS entities
*/
async function exportToGraphQLSchema(vfs) {
const entities = await vfs.listEntities()
const types = new Map()
// Group entities by type
for (const entity of entities) {
const type = entity.type || 'Unknown'
if (!types.has(type)) {
types.set(type, [])
}
types.get(type).push(entity)
}
// Generate schema
let schema = 'type Query {\n'
for (const [typeName, entities] of types) {
schema += ` get${typeName}(id: ID!): ${typeName}\n`
schema += ` list${typeName}s: [${typeName}!]!\n`
}
schema += '}\n\n'
// Generate types
for (const [typeName, entities] of types) {
schema += `type ${typeName} {\n`
schema += ' id: ID!\n'
// Infer fields from first entity
if (entities.length > 0) {
const sample = entities[0]
for (const [key, value] of Object.entries(sample)) {
if (key !== 'id') {
const fieldType = inferGraphQLType(value)
schema += ` ${key}: ${fieldType}\n`
}
}
}
schema += '}\n\n'
}
return schema
}
```
## Project Management Functions
### Get Project Insights
```javascript
/**
* Analyze project for insights and patterns
*/
async function getProjectInsights(vfs, projectPath) {
const stats = await vfs.getProjectStats(projectPath)
const todos = await vfs.getAllTodos(projectPath)
const timeline = await vfs.getTimeline({ limit: 100 })
// Analyze activity patterns
const activityByDay = {}
const activityByUser = {}
const activityByFile = {}
for (const event of timeline) {
const day = event.timestamp.toISOString().split('T')[0]
activityByDay[day] = (activityByDay[day] || 0) + 1
const user = event.user || 'system'
activityByUser[user] = (activityByUser[user] || 0) + 1
activityByFile[event.path] = (activityByFile[event.path] || 0) + 1
}
// Find hotspots (most edited files)
const hotspots = Object.entries(activityByFile)
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
.map(([path, count]) => ({ path, edits: count }))
// Todo analysis
const todosByPriority = {}
const todosByStatus = {}
for (const todo of todos) {
todosByPriority[todo.priority] = (todosByPriority[todo.priority] || 0) + 1
todosByStatus[todo.status] = (todosByStatus[todo.status] || 0) + 1
}
return {
stats,
activity: {
byDay: activityByDay,
byUser: activityByUser,
hotspots
},
todos: {
total: todos.length,
byPriority: todosByPriority,
byStatus: todosByStatus,
highPriority: todos.filter(t => t.priority === 'high' && t.status === 'pending')
},
recommendations: generateRecommendations(stats, todos, hotspots)
}
}
function generateRecommendations(stats, todos, hotspots) {
const recommendations = []
if (stats.largestFile && stats.largestFile.size > 1024 * 1024) {
recommendations.push({
type: 'refactor',
message: `Consider splitting ${stats.largestFile.path} (${Math.round(stats.largestFile.size / 1024)}KB)`
})
}
if (todos.filter(t => t.priority === 'high' && t.status === 'pending').length > 5) {
recommendations.push({
type: 'priority',
message: 'You have many high-priority pending todos'
})
}
if (hotspots.length > 0 && hotspots[0].edits > 50) {
recommendations.push({
type: 'stability',
message: `${hotspots[0].path} changes frequently, consider stabilizing`
})
}
return recommendations
}
```
### Generate Sprint Report
```javascript
/**
* Generate a report for the current sprint
*/
async function generateSprintReport(vfs, sprintStart, sprintEnd = new Date()) {
const timeline = await vfs.getTimeline({
from: sprintStart,
to: sprintEnd
})
const todos = await vfs.getAllTodos()
// Group work by user
const workByUser = {}
for (const event of timeline) {
const user = event.user || 'system'
if (!workByUser[user]) {
workByUser[user] = {
commits: 0,
filesModified: new Set(),
linesChanged: 0
}
}
workByUser[user].commits++
workByUser[user].filesModified.add(event.path)
}
// Calculate completion rate
const completedTodos = todos.filter(t => t.status === 'completed').length
const totalTodos = todos.length
const completionRate = totalTodos > 0 ? (completedTodos / totalTodos * 100).toFixed(1) : 0
return {
period: {
start: sprintStart,
end: sprintEnd,
days: Math.ceil((sprintEnd - sprintStart) / (1000 * 60 * 60 * 24))
},
team: Object.entries(workByUser).map(([user, work]) => ({
user,
commits: work.commits,
filesModified: work.filesModified.size
})),
todos: {
completed: completedTodos,
total: totalTodos,
completionRate: `${completionRate}%`,
remaining: todos.filter(t => t.status === 'pending')
},
velocity: {
commitsPerDay: (timeline.length / 7).toFixed(1),
todosPerDay: (completedTodos / 7).toFixed(1)
}
}
}
```
## Creative Writing Functions
### Track Character Arcs
```javascript
/**
* Track how characters evolve throughout a story
*/
async function trackCharacterArc(vfs, characterName, storyPath = '/') {
// Find the character entity
const entities = await vfs.searchEntities({
type: 'character',
name: characterName
})
if (entities.length === 0) {
throw new Error(`Character ${characterName} not found`)
}
const character = entities[0]
const occurrences = await vfs.findEntityOccurrences(character.id)
// Analyze each appearance
const arc = []
for (const occurrence of occurrences) {
const content = await vfs.readFile(occurrence.path)
const text = content.toString()
// Find mentions of the character (basic approach)
const mentions = text.split('\n').filter(line =>
line.toLowerCase().includes(characterName.toLowerCase())
)
arc.push({
chapter: occurrence.path,
mentions: mentions.length,
// Analyze emotional tone (simplified)
mood: analyzeMood(mentions),
// Extract key actions
actions: extractActions(mentions, characterName)
})
}
return {
character: character.metadata,
arc: arc,
summary: summarizeArc(arc)
}
}
function analyzeMood(mentions) {
const positiveWords = ['smiled', 'laughed', 'happy', 'joy', 'love', 'success']
const negativeWords = ['cried', 'angry', 'sad', 'fear', 'fail', 'death']
let positive = 0, negative = 0
for (const mention of mentions) {
const lower = mention.toLowerCase()
positive += positiveWords.filter(w => lower.includes(w)).length
negative += negativeWords.filter(w => lower.includes(w)).length
}
if (positive > negative) return 'positive'
if (negative > positive) return 'negative'
return 'neutral'
}
```
### Generate Story Bible
```javascript
/**
* Create a comprehensive reference for your story universe
*/
async function generateStoryBible(vfs, storyPath) {
const characters = await vfs.listEntities({ type: 'character' })
const locations = await vfs.listEntities({ type: 'location' })
const concepts = await vfs.findConcepts({ domain: 'narrative' })
const bible = {
title: 'Story Bible',
generated: new Date(),
characters: {},
locations: {},
plotThreads: {},
timeline: []
}
// Document characters
for (const char of characters) {
const occurrences = await vfs.findEntityOccurrences(char.id)
bible.characters[char.metadata.name] = {
...char.metadata,
appearances: occurrences.map(o => o.path),
relationships: await vfs.getEntityGraph(char.id, { depth: 1 })
}
}
// Document locations
for (const loc of locations) {
bible.locations[loc.metadata.name] = {
...loc.metadata,
scenes: await vfs.findEntityOccurrences(loc.id)
}
}
// Plot threads from concepts
for (const concept of concepts) {
if (concept.type === 'plot') {
bible.plotThreads[concept.name] = {
description: concept.description,
keywords: concept.keywords,
chapters: await vfs.findByConcept(concept.name)
}
}
}
// Generate timeline
const events = await vfs.getTimeline({ limit: 1000 })
bible.timeline = events.map(e => ({
date: e.timestamp,
event: e.description,
chapter: e.path
}))
return bible
}
```
## Game Development Functions
### Validate Game Data
```javascript
/**
* Validate game configuration files for consistency
*/
async function validateGameData(vfs, gamePath) {
const errors = []
const warnings = []
// Load all game data
const gameData = await vfs.exportToJSON(gamePath)
// Check quest references
if (gameData.quests) {
for (const [questName, quest] of Object.entries(gameData.quests)) {
// Check NPC references
if (quest._content?.questGiver) {
const npcPath = `${gamePath}/npcs/${quest._content.questGiver}.json`
const exists = await vfs.exists(npcPath)
if (!exists) {
errors.push(`Quest ${questName} references missing NPC: ${quest._content.questGiver}`)
}
}
// Check item rewards
if (quest._content?.rewards?.items) {
for (const item of quest._content.rewards.items) {
const itemPath = `${gamePath}/items/${item}.json`
const exists = await vfs.exists(itemPath)
if (!exists) {
warnings.push(`Quest ${questName} rewards missing item: ${item}`)
}
}
}
}
}
// Check balance
if (gameData.items) {
const itemPowers = []
for (const [itemName, item] of Object.entries(gameData.items)) {
if (item._content?.stats) {
const totalPower = Object.values(item._content.stats)
.reduce((a, b) => a + b, 0)
itemPowers.push({ name: itemName, power: totalPower })
}
}
// Find outliers
const avgPower = itemPowers.reduce((a, b) => a + b.power, 0) / itemPowers.length
const outliers = itemPowers.filter(i => Math.abs(i.power - avgPower) > avgPower * 2)
for (const outlier of outliers) {
warnings.push(`Item ${outlier.name} may be imbalanced (power: ${outlier.power}, avg: ${avgPower})`)
}
}
return { errors, warnings, valid: errors.length === 0 }
}
```
### Generate Loot Tables
```javascript
/**
* Generate weighted loot tables from item definitions
*/
async function generateLootTables(vfs, itemsPath) {
const items = await vfs.exportToJSON(itemsPath)
const tables = {
common: [],
uncommon: [],
rare: [],
epic: [],
legendary: []
}
for (const [itemName, item] of Object.entries(items)) {
if (item._meta?.type === 'file' && item._content?.rarity) {
const entry = {
name: itemName.replace('.json', ''),
weight: getWeight(item._content.rarity),
data: item._content
}
tables[item._content.rarity.toLowerCase()].push(entry)
}
}
// Normalize weights
for (const table of Object.values(tables)) {
const totalWeight = table.reduce((a, b) => a + b.weight, 0)
for (const entry of table) {
entry.probability = (entry.weight / totalWeight * 100).toFixed(2) + '%'
}
}
return tables
}
function getWeight(rarity) {
const weights = {
common: 100,
uncommon: 50,
rare: 20,
epic: 5,
legendary: 1
}
return weights[rarity.toLowerCase()] || 10
}
```
## Using These Functions
All these functions are templates that you can customize for your specific needs. To use them:
1. Copy the function you need
2. Modify it for your specific requirements
3. Use it with your VFS instance:
```javascript
import { Brainy } from '@soulcraft/brainy'
// Initialize VFS
const brain = new Brainy()
await brain.init()
const vfs = brain.vfs()
await vfs.init()
// Use your custom function
const insights = await getProjectInsights(vfs, '/my-project')
console.log(insights.recommendations)
// Combine multiple functions
const deps = await getDependencyGraph(vfs, '/src')
const cycles = await findCircularDependencies(vfs, '/src')
const untested = await findUntestedCode(vfs, '/src', '/tests')
```
Remember: These are starting points. The power of VFS is that you can combine its primitives to build exactly what you need for your domain!

View file

@ -1,684 +0,0 @@
# VFS Examples and Scenarios
> **⚠️ DOCUMENTATION IN PROGRESS**: This document is being updated to reflect recent VFS architecture changes. Many examples below reference deprecated Knowledge Layer methods. Updated examples coming soon. For current VFS capabilities, see [VFS_CORE.md](./VFS_CORE.md) and [SEMANTIC_VFS.md](./SEMANTIC_VFS.md).
## Real-World Scenarios
This document demonstrates how VFS enables powerful real-world applications with semantic search, relationships, and AI-powered concept extraction.
### Legend
- ✅ **Real VFS methods** - Fully implemented and working
- 📝 **User functions** - Templates available in [USER_FUNCTIONS.md](./USER_FUNCTIONS.md)
- 🔮 **Future features** - Not yet available (AI augmentations)
**Note:** All ✅ marked methods are production-ready. For 📝 methods, see USER_FUNCTIONS.md for implementation templates.
## Scenario 1: Collaborative Novel Writing
Multiple authors working on a shared universe with recurring characters and locations.
```javascript
import { Brainy } from '@soulcraft/brainy'
async function novelWritingProject() {
const brain = new Brainy({ storage: { type: 'file', path: './novel-data' } })
await brain.init()
const vfs = brain.vfs()
await vfs.init()
// Create project structure ✅
await vfs.mkdir('/novel')
await vfs.mkdir('/novel/chapters')
await vfs.mkdir('/novel/characters')
await vfs.mkdir('/novel/worldbuilding')
// Define main characters as files with rich metadata ✅
await vfs.writeFile('/novel/characters/elena-blackwood.md', `
# Elena Blackwood
A skilled detective with a mysterious past.
## Character Profile
- Age: 32
- Occupation: Private Investigator
- Skills: Deduction, combat, languages
- Personality: Determined, secretive, compassionate
`, {
metadata: {
characterType: 'protagonist',
tags: ['detective', 'mysterious', 'protagonist']
}
})
await vfs.writeFile('/novel/characters/marcus-void.md', `
# Marcus Void
description: 'A wealthy industrialist with dark secrets',
attributes: {
age: 45,
occupation: 'CEO of Void Industries',
traits: ['ruthless', 'charismatic', 'brilliant']
}
})
// Create location entities
const city = await vfs.createEntity({
name: 'Neo Tokyo',
type: 'location',
description: 'A sprawling cyberpunk metropolis',
attributes: {
population: '40 million',
districts: ['Shibuya-5', 'Crypto Quarter', 'Old Town'],
atmosphere: 'neon-lit, rain-soaked, vertical'
}
})
// Link entities ✅
await vfs.linkEntities(protagonist.id, city.id, 'lives_in')
await vfs.linkEntities(protagonist.id, antagonist.id, 'investigates')
// Write chapters with automatic entity tracking
await vfs.writeFile('/novel/chapters/chapter1.md', `
# Chapter 1: Rain in Neo Tokyo
Elena Blackwood stood at the edge of Shibuya-5, watching the rain cascade down
the neon-lit towers. The case file on Marcus Void burned in her pocket.
She had been tracking Void Industries for months, following a trail of
disappeared scientists and mysterious experiments. Tonight, she would finally
infiltrate the Crypto Quarter facility.
`)
// Multiple authors can work simultaneously ✅
vfs.setUser('author-alice')
await vfs.writeFile('/novel/chapters/chapter2.md', `
# Chapter 2: The Void Industries Tower
Marcus Void gazed down at Neo Tokyo from his penthouse office. Somewhere
in those rain-slicked streets, Elena Blackwood was hunting him...
`)
vfs.setUser('author-bob')
await vfs.appendFile('/novel/chapters/chapter2.md', `
He smiled. Let her come. The trap was already set.
`)
// Track character appearances across chapters ✅
const elenaAppearances = await vfs.findEntityOccurrences(protagonist.id)
console.log('Elena appears in:', elenaAppearances.map(f => f.path))
// Find all locations mentioned ✅
const locations = await vfs.listEntities({ type: 'location' })
// Generate character relationship graph ✅
const relationships = await vfs.getEntityGraph(protagonist.id, { depth: 2 })
// Track plot threads using concepts ✅
await vfs.createConcept({
name: 'The Void Conspiracy',
type: 'plot',
domain: 'narrative',
description: 'The main mystery involving disappeared scientists',
keywords: ['scientists', 'experiments', 'void industries', 'conspiracy']
})
// Find all chapters related to the conspiracy ✅
const conspiracyChapters = await vfs.findByConcept('The Void Conspiracy')
// Version control for revisions ✅
const chapterVersions = await vfs.getVersions('/novel/chapters/chapter1.md')
// Collaborative editing history ✅
const history = await vfs.getCollaborationHistory('/novel/chapters/chapter2.md')
console.log('Chapter 2 edited by:', history.map(h => h.user))
// Export for publishing ✅
const manuscript = await vfs.exportToMarkdown('/novel/chapters')
await vfs.close()
await brain.close()
}
```
## Scenario 2: Video Game Development
Game developers creating a complex RPG with quests, items, and NPCs.
```javascript
async function gameDevProject() {
const brain = new Brainy({ storage: { type: 's3', bucket: 'game-assets' } })
await brain.init()
const vfs = brain.vfs()
await vfs.init()
// Game project structure
await vfs.mkdir('/game')
await vfs.mkdir('/game/scripts')
await vfs.mkdir('/game/assets')
await vfs.mkdir('/game/quests')
await vfs.mkdir('/game/npcs')
await vfs.mkdir('/game/items')
// Define game systems as concepts
await vfs.createConcept({
name: 'Combat System',
type: 'system',
domain: 'gameplay',
keywords: ['damage', 'health', 'attacks', 'defense']
})
await vfs.createConcept({
name: 'Quest System',
type: 'system',
domain: 'gameplay',
keywords: ['objectives', 'rewards', 'dialogue', 'progression']
})
// Create NPC entities
const questGiver = await vfs.createEntity({
name: 'Elder Sage',
type: 'npc',
description: 'Wise old man who gives the main quest',
attributes: {
location: 'Village Square',
questsOffered: ['The Ancient Artifact', 'Lost Knowledge'],
dialogue: {
greeting: "Welcome, young adventurer...",
questStart: "I have a task of great importance..."
}
}
})
// Create item entities
const artifact = await vfs.createEntity({
name: 'Crystal of Power',
type: 'item',
description: 'A legendary artifact with immense magical power',
attributes: {
rarity: 'Legendary',
stats: { magic: 100, wisdom: 50 },
questItem: true,
lore: 'Forged in the age of dragons...'
}
})
// Write quest scripts
await vfs.writeFile('/game/quests/main_quest.json', JSON.stringify({
id: 'main_quest_01',
name: 'The Ancient Artifact',
description: 'Retrieve the Crystal of Power from the Dark Tower',
objectives: [
{ id: 'obj_1', description: 'Speak to the Elder Sage' },
{ id: 'obj_2', description: 'Travel to the Dark Tower' },
{ id: 'obj_3', description: 'Defeat the Guardian' },
{ id: 'obj_4', description: 'Retrieve the Crystal of Power' }
],
rewards: {
experience: 5000,
gold: 1000,
items: ['Crystal of Power']
}
}, null, 2))
// Link quest elements
await vfs.linkEntities(questGiver.id, 'main_quest_01', 'gives_quest')
await vfs.linkEntities('main_quest_01', artifact.id, 'rewards_item')
// Combat script with system tracking
await vfs.writeFile('/game/scripts/combat.js', `
class CombatSystem {
calculateDamage(attacker, defender) {
const baseDamage = attacker.stats.attack
const defense = defender.stats.defense
return Math.max(1, baseDamage - defense)
}
executeAttack(attacker, defender) {
const damage = this.calculateDamage(attacker, defender)
defender.health -= damage
return { damage, remaining: defender.health }
}
}
export default CombatSystem
`)
// Asset management ✅
await vfs.writeFile('/game/assets/sprites/elder_sage.png', spriteData)
await vfs.setMetadata('/game/assets/sprites/elder_sage.png', {
dimensions: '64x64',
animations: ['idle', 'talking'],
artist: 'Alice',
license: 'CC-BY-4.0'
})
// Track dependencies ✅
await vfs.addRelationship('/game/quests/main_quest.json', '/game/npcs/elder_sage.json', 'uses')
await vfs.addRelationship('/game/scripts/combat.js', '/game/systems/stats.js', 'imports')
// Find all content related to combat ✅
const combatFiles = await vfs.findByConcept('Combat System')
// Get all NPCs in a specific location ✅
const villageNPCs = await vfs.searchEntities({
type: 'npc',
where: { 'attributes.location': 'Village Square' }
})
// Track game balance changes — read the file as it was a week ago ✅
const lastWeek = await brain.asOf(new Date(Date.now() - 7 * 86_400_000))
const oldBalance = await lastWeek.find({ where: { path: '/game/data/balance.json' }, limit: 1 })
await lastWeek.release()
// Collaborative development tracking ✅
await vfs.addTodo('/game/quests/main_quest.json', {
task: 'Add voice dialogue triggers',
priority: 'medium',
status: 'pending',
assignee: 'audio-team'
})
// Export for build system ✅
const gameData = await vfs.exportToJSON('/game')
await vfs.close()
await brain.close()
}
```
## Scenario 3: Software Development Project
Building a web application with full project management.
```javascript
async function softwareProject() {
const brain = new Brainy({ storage: { type: 'r2', bucket: 'project-files' } })
await brain.init()
const vfs = brain.vfs()
await vfs.init()
// Import existing git repository ✅
await vfs.importFromGit('/local/repos/webapp', '/project')
// Define architectural concepts
await vfs.createConcept({
name: 'Authentication',
type: 'architecture',
domain: 'backend',
keywords: ['jwt', 'login', 'session', 'oauth'],
relatedConcepts: ['Security', 'User Management']
})
await vfs.createConcept({
name: 'State Management',
type: 'architecture',
domain: 'frontend',
keywords: ['redux', 'context', 'store', 'actions']
})
// Write source code
await vfs.writeFile('/project/src/auth/login.ts', `
import { User } from '../models/User'
import { generateJWT } from '../utils/jwt'
export async function login(email: string, password: string): Promise<string> {
const user = await User.findByEmail(email)
if (!user || !user.verifyPassword(password)) {
throw new Error('Invalid credentials')
}
return generateJWT(user.id)
}
`)
// Track imports and dependencies
await vfs.addRelationship('/project/src/auth/login.ts', '/project/src/models/User.ts', 'imports')
await vfs.addRelationship('/project/src/auth/login.ts', '/project/src/utils/jwt.ts', 'imports')
// Add inline TODOs
await vfs.addTodo('/project/src/auth/login.ts', {
task: 'Add rate limiting',
priority: 'high',
status: 'pending',
assignee: 'security-team',
due: '2025-02-01'
})
await vfs.addTodo('/project/src/auth/login.ts', {
task: 'Implement OAuth providers',
priority: 'medium',
status: 'in_progress',
assignee: 'backend-team'
})
// Configuration files
await vfs.writeFile('/project/config/database.json', JSON.stringify({
development: {
host: 'localhost',
port: 5432,
database: 'webapp_dev'
},
production: {
host: '${DB_HOST}',
port: 5432,
database: 'webapp_prod'
}
}, null, 2))
// Set security metadata
await vfs.setMetadata('/project/config/database.json', {
sensitive: true,
environment: 'all',
lastReviewed: '2025-01-15',
reviewer: 'security-team'
})
// Tests with relationship to source
await vfs.writeFile('/project/tests/auth/login.test.ts', `
import { login } from '../../src/auth/login'
describe('Authentication', () => {
test('valid login returns JWT', async () => {
const token = await login('user@example.com', 'password')
expect(token).toBeDefined()
})
})
`)
await vfs.addRelationship('/project/tests/auth/login.test.ts', '/project/src/auth/login.ts', 'tests')
// Documentation
await vfs.writeFile('/project/docs/API.md', `
# API Documentation
## Authentication
### POST /api/login
Authenticates a user and returns a JWT token.
**Request:**
\`\`\`json
{
"email": "user@example.com",
"password": "secret"
}
\`\`\`
`)
// Find all files needing security review ✅
const securityFiles = await vfs.search('authentication password jwt oauth', {
path: '/project/src',
type: 'file'
})
// Get project insights 📝 (see USER_FUNCTIONS.md for getProjectInsights)
const insights = await getProjectInsights(vfs, '/project') // User function
console.log('Most modified files:', insights.hotspots)
console.log('Key concepts:', insights.concepts)
console.log('Team activity:', insights.contributors)
// Find circular dependencies 📝 (see USER_FUNCTIONS.md)
const circularDeps = await findCircularDependencies(vfs, '/project/src') // User function
// Get test coverage relationships 📝 (see USER_FUNCTIONS.md)
const untested = await findUntestedCode(vfs, '/project/src') // User function
// Track technical debt ✅
const todos = await vfs.getAllTodos('/project')
const highPriorityDebt = todos.filter(t => t.priority === 'high' && t.status === 'pending')
// Generate dependency graph 📝 (see USER_FUNCTIONS.md)
const depGraph = await getDependencyGraph(vfs, '/project/src') // User function
// Find similar code (potential refactoring) 📝 (see USER_FUNCTIONS.md)
const similarCode = await findSimilarCode(vfs, '/project/src/auth/login.ts', {
threshold: 0.8,
minLines: 10
}) // User function
// Export for CI/CD ✅ (Knowledge Layer provides wrapper)
await vfs.exportToGit('/project', '/tmp/build-output')
// Collaborative features ✅ / 🔮
vfs.setUser('developer-alice') // ✅ Real method
// const conflicts = await vfs.detectConflicts('/project/src/auth/login.ts') // 🔮 Future feature
await vfs.close()
await brain.close()
}
```
## Scenario 4: Multi-Project Knowledge Base
All projects in one Brainy instance, sharing concepts and cross-referencing.
```javascript
async function unifiedKnowledgeBase() {
const brain = new Brainy({
storage: { type: 'file', path: './knowledge-base' }
})
await brain.init()
const vfs = brain.vfs()
await vfs.init()
// Create separate project spaces
await vfs.mkdir('/novel')
await vfs.mkdir('/game')
await vfs.mkdir('/webapp')
// Shared universe - characters appear in both novel and game
const sharedCharacter = await vfs.createEntity({
name: 'Elena Blackwood',
type: 'character',
description: 'Protagonist appearing in multiple works'
})
// Novel chapter mentioning Elena
await vfs.writeFile('/novel/chapter1.md', `
Elena Blackwood stood at the edge of the digital void...
`)
// Game script featuring Elena as NPC
await vfs.writeFile('/game/npcs/elena.json', JSON.stringify({
name: 'Elena Blackwood',
type: 'ally',
dialogue: ['I\'ve seen this before in my world...']
}))
// Web app has Elena as example user
await vfs.writeFile('/webapp/seeds/users.json', JSON.stringify([{
name: 'Elena Blackwood',
email: 'elena@example.com',
role: 'detective'
}]))
// Cross-project entity tracking ✅
const elenaOccurrences = await vfs.findEntityOccurrences(sharedCharacter.id)
console.log('Elena appears across projects:', elenaOccurrences)
// Shared technical concepts
const authConcept = await vfs.createConcept({
name: 'Authentication',
type: 'technical',
domain: 'software'
})
// Find auth implementations across all projects ✅
const authImplementations = await vfs.findByConcept('Authentication')
// Returns: /webapp/src/auth.js, /game/scripts/player-auth.js, etc.
// Cross-project relationships ✅
await vfs.addRelationship('/novel/chapter1.md', '/game/story/intro.txt', 'inspires')
await vfs.addRelationship('/game/npcs/elena.json', '/novel/characters/elena.md', 'based_on')
// Universal search across all projects ✅
const results = await vfs.search('Elena Blackwood authentication', {
path: '/',
recursive: true
})
// Project statistics 📝 (see USER_FUNCTIONS.md for getProjectStats)
const novelStats = await getProjectStats(vfs, '/novel') // User function
const gameStats = await getProjectStats(vfs, '/game') // User function
const webappStats = await getProjectStats(vfs, '/webapp') // User function
console.log('Total files:', novelStats.fileCount + gameStats.fileCount + webappStats.fileCount)
console.log('Total size:', novelStats.totalSize + gameStats.totalSize + webappStats.totalSize)
// Knowledge graph visualization data 🔮 (future feature)
// const knowledgeGraph = await vfs.getGlobalKnowledgeGraph() // Not yet implemented
// Returns nodes (files, entities, concepts) and edges (relationships)
// Find connections between projects 🔮 (future feature)
// const crossProjectLinks = await vfs.findCrossProjectLinks() // Not yet implemented
// Unified timeline ✅
const timeline = await vfs.getTimeline({
from: '2025-01-01',
to: '2025-12-31'
})
await vfs.close()
await brain.close()
}
```
## Advanced Features
### Semantic Code Analysis 📝
These are user functions - see [USER_FUNCTIONS.md](./USER_FUNCTIONS.md) for implementation templates:
```javascript
// Find security vulnerabilities (user function example)
const vulnerabilities = await findPatterns(vfs, [
'eval(',
'innerHTML =',
'SQL injection',
'hardcoded password'
])
// Find code smells (user function example)
const codeSmells = await analyzeCodeQuality(vfs, '/src', {
checkDuplication: true,
checkComplexity: true,
checkNaming: true
})
```
### AI-Powered Features 🔮
**Note:** These features require AI integration and are not yet available.
```javascript
// Future: Generate documentation
// const docs = await vfs.generateDocumentation('/src/auth/login.ts')
// Future: Suggest refactorings
// const refactorings = await vfs.suggestRefactorings('/src/utils.js')
// Future: Auto-complete code
// const completion = await vfs.completeCode('/src/api.ts', { line: 42, column: 10 })
```
### Migration and Backup 🔮
**Note:** These features are planned but not yet implemented.
```javascript
// Future: Backup with full history
// await vfs.createBackup('/backup/2025-01-20.brainy')
// Future: Migrate between storage backends
// const migration = await vfs.migrate({
// from: { type: 'file', path: './old-data' },
// to: { type: 's3', bucket: 'new-bucket' }
// })
// Future: Incremental sync
// await vfs.sync('/local/path', '/vfs/path', {
// bidirectional: true,
// conflictStrategy: 'newest'
// })
```
### Performance at Scale
```javascript
// Handle millions of files ✅
for (let i = 0; i < 1000000; i++) {
await vfs.writeFile(`/data/file${i}.txt`, `Content ${i}`)
// Uses chunking, compression, and efficient indexing
}
// Fast parallel operations ✅
await Promise.all([
vfs.writeFile('/file1.txt', 'data1'),
vfs.writeFile('/file2.txt', 'data2'),
vfs.writeFile('/file3.txt', 'data3')
])
// Bulk write operations ✅
const files = [
{ path: '/data/file1.txt', content: 'Content 1' },
{ path: '/data/file2.txt', content: 'Content 2' },
// ... more files
]
await vfs.bulkWrite(files)
// Bulk imports 🔮 (future feature)
// await vfs.bulkImport('/massive/dataset', {
// parallel: 10,
// batchSize: 1000,
// progress: (count, total) => console.log(`${count}/${total}`)
// })
```
## Implementation Status
### ✅ Fully Implemented Features
All methods marked with ✅ are production-ready:
1. **Core VFS Operations**: mkdir, writeFile, readFile, appendFile, stat, readdir, etc.
2. **Entity System**: createEntity, linkEntities, findEntityOccurrences, updateEntity, getEntityGraph
3. **Concept System**: createConcept, findByConcept
4. **Knowledge Layer**: Event recording, semantic versioning, collaboration tracking
5. **Search**: Triple Intelligence (vector + field + graph)
6. **Git Integration**: importFromGit, exportToGit
7. **Export Formats**: exportToMarkdown, exportToJSON
8. **Bulk Operations**: bulkWrite for efficient batch processing
9. **Project Management**: todos, metadata, relationships
### 📝 User Functions
Methods marked with 📝 are domain-specific functions that you can implement using VFS primitives. See [USER_FUNCTIONS.md](./USER_FUNCTIONS.md) for ready-to-use templates:
- **Code Analysis**: getDependencyGraph, findCircularDependencies, findUntestedCode, findSimilarCode
- **Creative Writing**: trackCharacterArc, generateStoryBible
- **Game Development**: validateGameData, generateLootTables
- **Project Management**: getProjectInsights, generateSprintReport
- **Export Formats**: exportToEpub, exportToStaticSite
### 🔮 Future Features
Methods marked with 🔮 require AI integration or are planned for future releases:
- **AI-Powered**: generateDocumentation, suggestRefactorings, completeCode
- **Advanced Analysis**: detectConflicts, getGlobalKnowledgeGraph, findCrossProjectLinks
- **Migration Tools**: createBackup, migrate, sync, bulkImport
## Real Implementation Guarantees
- **No Mocks**: Every ✅ method is fully functional
- **Real Storage**: Uses Brainy entities with embeddings
- **Real Search**: Triple Intelligence combining vectors, fields, and graphs
- **Real Relationships**: Graph-based connections via brain.relate()
- **Production Ready**: Complete error handling, async/await, resource cleanup
The VFS + Knowledge Layer combination provides a solid foundation for intelligent applications. Use the ✅ methods directly, implement 📝 functions as needed for your domain, and stay tuned for 🔮 features.

View file

@ -118,13 +118,13 @@ const results = await brain.search('machine learning', {
### 2. Graph Traversal ### 2. Graph Traversal
```javascript ```javascript
// Find all entities contained in a directory // Find all entities contained in a directory
const contained = await brain.getRelations({ const contained = await brain.related({
from: directoryId, from: directoryId,
type: VerbType.Contains type: VerbType.Contains
}) })
// Find what contains a file (parent directories) // Find what contains a file (parent directories)
const parents = await brain.getRelations({ const parents = await brain.related({
to: fileId, to: fileId,
type: VerbType.Contains type: VerbType.Contains
}) })

View file

@ -1,25 +0,0 @@
{
"_name_or_path": "sentence-transformers/all-MiniLM-L6-v2",
"architectures": [
"BertModel"
],
"attention_probs_dropout_prob": 0.1,
"classifier_dropout": null,
"gradient_checkpointing": false,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.1,
"hidden_size": 384,
"initializer_range": 0.02,
"intermediate_size": 1536,
"layer_norm_eps": 1e-12,
"max_position_embeddings": 512,
"model_type": "bert",
"num_attention_heads": 12,
"num_hidden_layers": 6,
"pad_token_id": 0,
"position_embedding_type": "absolute",
"transformers_version": "4.29.2",
"type_vocab_size": 2,
"use_cache": true,
"vocab_size": 30522
}

File diff suppressed because it is too large Load diff

View file

@ -1,15 +0,0 @@
{
"clean_up_tokenization_spaces": true,
"cls_token": "[CLS]",
"do_basic_tokenize": true,
"do_lower_case": true,
"mask_token": "[MASK]",
"model_max_length": 512,
"never_split": null,
"pad_token": "[PAD]",
"sep_token": "[SEP]",
"strip_accents": null,
"tokenize_chinese_chars": true,
"tokenizer_class": "BertTokenizer",
"unk_token": "[UNK]"
}

115
package-lock.json generated
View file

@ -10,24 +10,20 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@msgpack/msgpack": "^3.1.2", "@msgpack/msgpack": "^3.1.2",
"@types/js-yaml": "^4.0.9",
"boxen": "^8.0.1", "boxen": "^8.0.1",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"chardet": "^2.0.0", "chardet": "^2.0.0",
"cli-table3": "^0.6.5", "cli-table3": "^0.6.5",
"commander": "^11.1.0", "commander": "^11.1.0",
"csv-parse": "^6.1.0", "csv-parse": "^6.1.0",
"exifr": "^7.1.3",
"inquirer": "^12.9.3", "inquirer": "^12.9.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"mammoth": "^1.11.0", "mammoth": "^1.11.0",
"mime": "^4.1.0", "mime": "^4.1.0",
"ora": "^8.2.0", "ora": "^8.2.0",
"pdfjs-dist": "^4.0.379", "pdfjs-dist": "^4.0.379",
"probe-image-size": "^7.2.3",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"roaring-wasm": "^1.1.0", "roaring-wasm": "^1.1.0",
"uuid": "^9.0.1",
"ws": "^8.18.3", "ws": "^8.18.3",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
@ -40,9 +36,9 @@
"@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-replace": "^6.0.2",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@testcontainers/redis": "^11.5.1", "@testcontainers/redis": "^11.5.1",
"@types/js-yaml": "^4.0.9",
"@types/mime": "^3.0.4", "@types/mime": "^3.0.4",
"@types/node": "^20.11.30", "@types/node": "^20.11.30",
"@types/probe-image-size": "^7.2.5",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0",
@ -54,6 +50,7 @@
"testcontainers": "^11.5.1", "testcontainers": "^11.5.1",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"uuid": "^9.0.1",
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"engines": { "engines": {
@ -2496,6 +2493,7 @@
"version": "4.0.9", "version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
@ -2520,16 +2518,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/needle": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@types/needle/-/needle-3.3.0.tgz",
"integrity": "sha512-UFIuc1gdyzAqeVUYpSL+cliw2MmU/ZUhVZKE7Zo4wPbgc8hbljeKSnn6ls6iG8r5jpegPXLUIhJ+Wb2kLVs8cg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.19.27", "version": "20.19.27",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz",
@ -2554,17 +2542,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/probe-image-size": {
"version": "7.2.5",
"resolved": "https://registry.npmjs.org/@types/probe-image-size/-/probe-image-size-7.2.5.tgz",
"integrity": "sha512-9Bg6d/GNnjmhMMxadDstwrSlquuuLf0jQuPszbU6n3QUfybif3V/ryD3J2i9iaiC5JB/FU/8E41n88SM/UB+Tg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/needle": "*",
"@types/node": "*"
}
},
"node_modules/@types/raf": { "node_modules/@types/raf": {
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
@ -5403,12 +5380,6 @@
"bare-events": "^2.7.0" "bare-events": "^2.7.0"
} }
}, },
"node_modules/exifr": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/exifr/-/exifr-7.1.3.tgz",
"integrity": "sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==",
"license": "MIT"
},
"node_modules/expect-type": { "node_modules/expect-type": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
@ -6874,7 +6845,9 @@
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"license": "MIT" "dev": true,
"license": "MIT",
"peer": true
}, },
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "6.0.0", "version": "6.0.0",
@ -7447,6 +7420,7 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/mute-stream": { "node_modules/mute-stream": {
@ -7492,44 +7466,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/needle": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
"integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==",
"license": "MIT",
"dependencies": {
"debug": "^3.2.6",
"iconv-lite": "^0.4.4",
"sax": "^1.2.4"
},
"bin": {
"needle": "bin/needle"
},
"engines": {
"node": ">= 4.4.x"
}
},
"node_modules/needle/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/needle/node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/neo-async": { "node_modules/neo-async": {
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
@ -7920,17 +7856,6 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/probe-image-size": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz",
"integrity": "sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==",
"license": "MIT",
"dependencies": {
"lodash.merge": "^4.6.2",
"needle": "^2.5.2",
"stream-parser": "~0.3.1"
}
},
"node_modules/process": { "node_modules/process": {
"version": "0.11.10", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -8504,6 +8429,7 @@
"version": "1.4.3", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
"integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
"dev": true,
"license": "BlueOak-1.0.0" "license": "BlueOak-1.0.0"
}, },
"node_modules/semver": { "node_modules/semver": {
@ -8934,30 +8860,6 @@
"stream-chain": "^2.2.5" "stream-chain": "^2.2.5"
} }
}, },
"node_modules/stream-parser": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz",
"integrity": "sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==",
"license": "MIT",
"dependencies": {
"debug": "2"
}
},
"node_modules/stream-parser/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/stream-parser/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/streamx": { "node_modules/streamx": {
"version": "2.23.0", "version": "2.23.0",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
@ -9584,6 +9486,7 @@
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"dev": true,
"funding": [ "funding": [
"https://github.com/sponsors/broofa", "https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan" "https://github.com/sponsors/ctavan"

View file

@ -98,8 +98,6 @@
"lint:fix": "eslint --ext .ts,.js src/ --fix", "lint:fix": "eslint --ext .ts,.js src/ --fix",
"format": "prettier --write \"src/**/*.{ts,js}\"", "format": "prettier --write \"src/**/*.{ts,js}\"",
"format:check": "prettier --check \"src/**/*.{ts,js}\"", "format:check": "prettier --check \"src/**/*.{ts,js}\"",
"migrate:logger": "tsx scripts/migrate-to-structured-logger.ts",
"migrate:logger:dry": "tsx scripts/migrate-to-structured-logger.ts --dry-run",
"release": "./scripts/release.sh patch", "release": "./scripts/release.sh patch",
"release:patch": "./scripts/release.sh patch", "release:patch": "./scripts/release.sh patch",
"release:minor": "./scripts/release.sh minor", "release:minor": "./scripts/release.sh minor",
@ -160,9 +158,9 @@
"@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-replace": "^6.0.2",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@testcontainers/redis": "^11.5.1", "@testcontainers/redis": "^11.5.1",
"@types/js-yaml": "^4.0.9",
"@types/mime": "^3.0.4", "@types/mime": "^3.0.4",
"@types/node": "^20.11.30", "@types/node": "^20.11.30",
"@types/probe-image-size": "^7.2.5",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0",
@ -174,28 +172,25 @@
"testcontainers": "^11.5.1", "testcontainers": "^11.5.1",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"uuid": "^9.0.1",
"vitest": "^3.2.4" "vitest": "^3.2.4"
}, },
"dependencies": { "dependencies": {
"@msgpack/msgpack": "^3.1.2", "@msgpack/msgpack": "^3.1.2",
"@types/js-yaml": "^4.0.9",
"boxen": "^8.0.1", "boxen": "^8.0.1",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"chardet": "^2.0.0", "chardet": "^2.0.0",
"cli-table3": "^0.6.5", "cli-table3": "^0.6.5",
"commander": "^11.1.0", "commander": "^11.1.0",
"csv-parse": "^6.1.0", "csv-parse": "^6.1.0",
"exifr": "^7.1.3",
"inquirer": "^12.9.3", "inquirer": "^12.9.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"mammoth": "^1.11.0", "mammoth": "^1.11.0",
"mime": "^4.1.0", "mime": "^4.1.0",
"ora": "^8.2.0", "ora": "^8.2.0",
"pdfjs-dist": "^4.0.379", "pdfjs-dist": "^4.0.379",
"probe-image-size": "^7.2.3",
"prompts": "^2.4.2", "prompts": "^2.4.2",
"roaring-wasm": "^1.1.0", "roaring-wasm": "^1.1.0",
"uuid": "^9.0.1",
"ws": "^8.18.3", "ws": "^8.18.3",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },

View file

@ -1,60 +0,0 @@
#!/bin/bash
# Create GitHub release with unarchived model files for direct serving
# This allows transformers.js to download individual files directly
set -e
echo "🚀 Creating GitHub release with unarchived model files..."
# Create temporary directory
TEMP_DIR=$(mktemp -d)
cd "$TEMP_DIR"
echo "📥 Downloading existing model archive..."
curl -sL https://github.com/soulcraftlabs/brainy/releases/download/models-v1/all-MiniLM-L6-v2.tar.gz -o models.tar.gz
echo "📦 Extracting models..."
tar -xzf models.tar.gz
echo "🔧 Preparing individual model files for release..."
# Create properly structured directory
mkdir -p release-files/Xenova/all-MiniLM-L6-v2/onnx
# Copy files with correct naming for direct serving
cp all-MiniLM-L6-v2/config.json release-files/Xenova/all-MiniLM-L6-v2/
cp all-MiniLM-L6-v2/tokenizer.json release-files/Xenova/all-MiniLM-L6-v2/
cp all-MiniLM-L6-v2/tokenizer_config.json release-files/Xenova/all-MiniLM-L6-v2/
cp all-MiniLM-L6-v2/onnx/model.onnx release-files/Xenova/all-MiniLM-L6-v2/onnx/
# Also keep the tar.gz for backward compatibility
cp models.tar.gz release-files/
echo "📋 Files to upload:"
find release-files -type f -exec ls -lh {} \;
echo ""
echo "✅ Files ready for release!"
echo ""
echo "To create the release with individual files:"
echo ""
echo "1. Go to: https://github.com/soulcraftlabs/brainy/releases/new"
echo "2. Tag: models-v2"
echo "3. Title: Model Files v2 - Unarchived"
echo "4. Upload these files from $TEMP_DIR/release-files/:"
echo " - Xenova/all-MiniLM-L6-v2/config.json"
echo " - Xenova/all-MiniLM-L6-v2/tokenizer.json"
echo " - Xenova/all-MiniLM-L6-v2/tokenizer_config.json"
echo " - Xenova/all-MiniLM-L6-v2/onnx/model.onnx"
echo " - all-MiniLM-L6-v2.tar.gz (for compatibility)"
echo ""
echo "Or use GitHub CLI:"
echo " cd $TEMP_DIR/release-files"
echo " gh release create models-v2 --repo soulcraftlabs/brainy \\"
echo " --title 'Model Files v2 - Direct Serving' \\"
echo " --notes 'Unarchived model files for direct HTTP serving' \\"
echo " Xenova/all-MiniLM-L6-v2/config.json \\"
echo " Xenova/all-MiniLM-L6-v2/tokenizer.json \\"
echo " Xenova/all-MiniLM-L6-v2/tokenizer_config.json \\"
echo " Xenova/all-MiniLM-L6-v2/onnx/model.onnx \\"
echo " models.tar.gz"

View file

@ -1,194 +0,0 @@
/**
* Diagnostic script for Workshop type filtering issue
*
* This script mimics Workshop's exact import pattern to see what's happening
*/
import { Brainy, NounType } from '../src/index.js'
import * as fs from 'fs'
import * as path from 'path'
async function diagnose() {
console.log('\n🔬 Workshop Type Filtering Diagnostic\n')
console.log('='.repeat(70))
// Use temporary directory
const testDir = './test-workshop-data'
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true })
}
const brain = new Brainy({
storage: {
type: 'filesystem',
path: testDir
}
})
await brain.init()
console.log('\n1⃣ Testing WITHOUT VFS (control group)...\n')
// Add entities without VFS
console.log('Adding 3 person entities directly...')
await brain.add({ data: 'Person 1', type: NounType.Person, metadata: { name: 'Person 1' } })
await brain.add({ data: 'Person 2', type: NounType.Person, metadata: { name: 'Person 2' } })
await brain.add({ data: 'Person 3', type: NounType.Person, metadata: { name: 'Person 3' } })
console.log('Adding 2 location entities directly...')
await brain.add({ data: 'Location 1', type: NounType.Location, metadata: { name: 'Location 1' } })
await brain.add({ data: 'Location 2', type: NounType.Location, metadata: { name: 'Location 2' } })
console.log('\n📊 Testing type filtering on direct entities...\n')
const allDirect = await brain.find({ limit: 100 })
console.log(` Total entities: ${allDirect.length}`)
const peopleDirect = await brain.find({ type: NounType.Person, limit: 100 })
console.log(` Person filter: ${peopleDirect.length} (expected: 3)`)
const locationsDirect = await brain.find({ type: NounType.Location, limit: 100 })
console.log(` Location filter: ${locationsDirect.length} (expected: 2)`)
if (peopleDirect.length === 3 && locationsDirect.length === 2) {
console.log('\n ✅ Type filtering works on direct entities!')
} else {
console.log('\n ❌ Type filtering BROKEN on direct entities!')
return
}
console.log('\n' + '='.repeat(70))
console.log('\n2⃣ Testing WITH VFS (Workshop pattern)...\n')
// Simulate Workshop's pattern: create VFS file entities
console.log('Creating VFS file wrappers (like import does)...')
const vfs = brain.vfs()
await vfs.init()
// Create VFS directory
await vfs.mkdir('/imports/test', { recursive: true })
// Create VFS files with embedded entity data (mimics import)
console.log('Creating VFS file for Person entity...')
const personEntityData = {
id: 'ent_person_test',
name: 'John Smith',
type: 'person',
metadata: {
source: 'excel',
originalData: {
Name: 'John Smith',
_sheet: 'Characters'
}
}
}
await vfs.writeFile(
'/imports/test/john_smith.json',
Buffer.from(JSON.stringify(personEntityData, null, 2))
)
console.log('Creating VFS file for Location entity...')
const locationEntityData = {
id: 'ent_location_test',
name: 'New York',
type: 'location',
metadata: {
source: 'excel',
originalData: {
Name: 'New York',
_sheet: 'Places'
}
}
}
await vfs.writeFile(
'/imports/test/new_york.json',
Buffer.from(JSON.stringify(locationEntityData, null, 2))
)
console.log('\n📊 Checking what entities exist now...\n')
const allWithVfs = await brain.find({ limit: 100 })
console.log(` Total entities: ${allWithVfs.length}`)
// Analyze entity types
const typeCounts: Record<string, number> = {}
for (const result of allWithVfs) {
const type = result.type || 'unknown'
typeCounts[type] = (typeCounts[type] || 0) + 1
}
console.log('\n Entity types breakdown:')
for (const [type, count] of Object.entries(typeCounts)) {
console.log(` - ${type}: ${count}`)
}
console.log('\n📊 Testing type filtering with VFS entities...\n')
const peopleWithVfs = await brain.find({ type: NounType.Person, limit: 100 })
console.log(` Person filter: ${peopleWithVfs.length} (expected: 3 direct + maybe VFS?)`)
const locationsWithVfs = await brain.find({ type: NounType.Location, limit: 100 })
console.log(` Location filter: ${locationsWithVfs.length} (expected: 2 direct + maybe VFS?)`)
const documents = await brain.find({ type: NounType.Document, limit: 100 })
console.log(` Document filter: ${documents.length} (VFS wrappers?)`)
console.log('\n' + '='.repeat(70))
console.log('\n3⃣ Analyzing VFS wrapper structure...\n')
// Get a VFS wrapper entity
const vfsWrapper = allWithVfs.find(e => e.metadata?.vfsType === 'file')
if (vfsWrapper) {
console.log('Found VFS wrapper entity:')
console.log(` - ID: ${vfsWrapper.id}`)
console.log(` - Type: ${vfsWrapper.type}`)
console.log(` - VFS Type: ${vfsWrapper.metadata?.vfsType}`)
console.log(` - Has rawData: ${!!vfsWrapper.metadata?.rawData}`)
console.log(` - Path: ${vfsWrapper.metadata?.path}`)
if (vfsWrapper.metadata?.rawData) {
console.log('\n Decoding rawData...')
try {
const decoded = Buffer.from(vfsWrapper.metadata.rawData, 'base64').toString()
const entity = JSON.parse(decoded)
console.log(` - Embedded entity name: ${entity.name}`)
console.log(` - Embedded entity type: ${entity.type}`)
console.log(` - Wrapper type: ${vfsWrapper.type}`)
console.log('\n 🔍 KEY INSIGHT:')
console.log(` Wrapper has type="${vfsWrapper.type}"`)
console.log(` But embedded entity has type="${entity.type}"`)
console.log(' When you filter by person, you get the WRAPPER type, not embedded type!')
} catch (err) {
console.log(' ❌ Failed to decode rawData')
}
}
} else {
console.log('No VFS wrapper entities found')
}
console.log('\n' + '='.repeat(70))
console.log('\n4⃣ DIAGNOSIS\n')
if (documents.length > 0 && peopleWithVfs.length === 3) {
console.log('❌ FOUND THE BUG!')
console.log('')
console.log('VFS creates document wrappers with type="document".')
console.log('The actual entity data is stored as base64 in metadata.rawData.')
console.log('When you filter by type="person", you\'re filtering the WRAPPER type.')
console.log('Since wrappers are type="document", you get 0 results.')
console.log('')
console.log('This is a DESIGN ISSUE in how VFS import works!')
} else if (peopleWithVfs.length > 3) {
console.log('✅ Type filtering works correctly!')
console.log('VFS entities are being created with proper types.')
} else {
console.log('🤔 Unclear - need more investigation')
}
console.log('\n' + '='.repeat(70) + '\n')
// Cleanup
fs.rmSync(testDir, { recursive: true })
}
diagnose().catch(console.error)

View file

@ -1,323 +0,0 @@
#!/usr/bin/env node
/**
* Migration script to replace console.log statements with structured logger
* Usage: npm run migrate:logger [--dry-run] [--file=path/to/file.ts]
*/
import * as fs from 'fs/promises'
import * as path from 'path'
import { glob } from 'glob'
import { fileURLToPath } from 'url'
interface MigrationOptions {
dryRun: boolean
targetFile?: string
verbose: boolean
}
interface MigrationResult {
file: string
changes: number
errors: string[]
}
class LoggerMigrator {
private results: MigrationResult[] = []
constructor(private options: MigrationOptions) {}
async migrate(): Promise<void> {
console.log('🔄 Starting logger migration...')
const files = await this.getFilesToMigrate()
console.log(`Found ${files.length} TypeScript files to process`)
for (const file of files) {
await this.migrateFile(file)
}
this.printSummary()
}
private async getFilesToMigrate(): Promise<string[]> {
if (this.options.targetFile) {
return [this.options.targetFile]
}
// Get all TypeScript files excluding node_modules, dist, and test files
const pattern = 'src/**/*.ts'
const ignore = [
'**/node_modules/**',
'**/dist/**',
'**/*.test.ts',
'**/*.spec.ts',
'**/logger.ts',
'**/structuredLogger.ts'
]
return glob(pattern, { ignore })
}
private async migrateFile(filePath: string): Promise<void> {
const result: MigrationResult = {
file: filePath,
changes: 0,
errors: []
}
try {
const content = await fs.readFile(filePath, 'utf-8')
const moduleName = this.extractModuleName(filePath)
let modified = content
let changesMade = false
// Check if file already imports logger
const hasLoggerImport = /import.*(?:createModuleLogger|structuredLogger).*from/.test(content)
const hasConsoleUsage = /console\.(log|warn|error|info|debug)/.test(content)
if (!hasConsoleUsage) {
if (this.options.verbose) {
console.log(` ⏭️ ${filePath} - No console statements found`)
}
return
}
// Add import if needed
if (!hasLoggerImport && hasConsoleUsage) {
modified = this.addLoggerImport(modified)
changesMade = true
}
// Replace console statements
const patterns = [
{
// console.log with string literal
pattern: /console\.log\s*\(\s*(['"`])([^'"`]*)\1\s*(?:,\s*(.+?))?\s*\)/g,
replacement: (match: string, quote: string, message: string, args?: string) => {
result.changes++
return args
? `logger.info('${message}', ${args})`
: `logger.info('${message}')`
}
},
{
// console.error with string literal
pattern: /console\.error\s*\(\s*(['"`])([^'"`]*)\1\s*(?:,\s*(.+?))?\s*\)/g,
replacement: (match: string, quote: string, message: string, args?: string) => {
result.changes++
return args
? `logger.error('${message}', ${args})`
: `logger.error('${message}')`
}
},
{
// console.warn with string literal
pattern: /console\.warn\s*\(\s*(['"`])([^'"`]*)\1\s*(?:,\s*(.+?))?\s*\)/g,
replacement: (match: string, quote: string, message: string, args?: string) => {
result.changes++
return args
? `logger.warn('${message}', ${args})`
: `logger.warn('${message}')`
}
},
{
// console.info with string literal
pattern: /console\.info\s*\(\s*(['"`])([^'"`]*)\1\s*(?:,\s*(.+?))?\s*\)/g,
replacement: (match: string, quote: string, message: string, args?: string) => {
result.changes++
return args
? `logger.info('${message}', ${args})`
: `logger.info('${message}')`
}
},
{
// console.debug with string literal
pattern: /console\.debug\s*\(\s*(['"`])([^'"`]*)\1\s*(?:,\s*(.+?))?\s*\)/g,
replacement: (match: string, quote: string, message: string, args?: string) => {
result.changes++
return args
? `logger.debug('${message}', ${args})`
: `logger.debug('${message}')`
}
}
]
// Apply replacements
for (const { pattern, replacement } of patterns) {
const before = modified
modified = modified.replace(pattern, replacement as any)
if (before !== modified) {
changesMade = true
}
}
// Handle complex console statements that need manual review
const complexPatterns = [
/console\.(log|warn|error|info|debug)\s*\([^'"`]/g
]
for (const pattern of complexPatterns) {
const matches = modified.match(pattern)
if (matches) {
for (const match of matches) {
result.errors.push(`Complex console statement needs manual review: ${match.substring(0, 50)}...`)
// Add a TODO comment for manual review
modified = modified.replace(
match,
`// TODO: Migrate to structured logger\n ${match}`
)
}
}
}
// Add logger declaration after imports
if (changesMade && !hasLoggerImport) {
const importEndMatch = modified.match(/^((?:import.*\n)+)/m)
if (importEndMatch) {
const afterImports = importEndMatch.index! + importEndMatch[0].length
modified =
modified.slice(0, afterImports) +
`\nconst logger = createModuleLogger('${moduleName}')\n` +
modified.slice(afterImports)
}
}
// Write changes
if (changesMade && !this.options.dryRun) {
await fs.writeFile(filePath, modified, 'utf-8')
console.log(`${filePath} - ${result.changes} changes`)
} else if (changesMade) {
console.log(` 🔍 ${filePath} - ${result.changes} changes (dry run)`)
}
this.results.push(result)
} catch (error) {
result.errors.push(`Failed to process file: ${error}`)
this.results.push(result)
}
}
private addLoggerImport(content: string): string {
// Find the last import statement
const importMatches = [...content.matchAll(/^import.*$/gm)]
if (importMatches.length > 0) {
const lastImport = importMatches[importMatches.length - 1]
const insertPos = lastImport.index! + lastImport[0].length
const relativeImportPath = this.getRelativeImportPath()
const importStatement = `\nimport { createModuleLogger } from '${relativeImportPath}'`
return content.slice(0, insertPos) + importStatement + content.slice(insertPos)
}
// No imports found, add at the beginning
const relativeImportPath = this.getRelativeImportPath()
return `import { createModuleLogger } from '${relativeImportPath}'\n\n${content}`
}
private getRelativeImportPath(): string {
// This will be calculated based on the file being processed
// For now, return a placeholder
return '../utils/structuredLogger.js'
}
private extractModuleName(filePath: string): string {
// Extract module name from file path
const relativePath = path.relative('src', filePath)
const moduleName = relativePath
.replace(/\.ts$/, '')
.replace(/\//g, ':')
.replace(/\\+/g, ':')
return moduleName
}
private printSummary(): void {
console.log('\n📊 Migration Summary:')
console.log('=' .repeat(50))
let totalChanges = 0
let totalErrors = 0
let filesWithChanges = 0
let filesWithErrors = 0
for (const result of this.results) {
if (result.changes > 0) {
filesWithChanges++
totalChanges += result.changes
}
if (result.errors.length > 0) {
filesWithErrors++
totalErrors += result.errors.length
console.log(`\n⚠ ${result.file}:`)
for (const error of result.errors) {
console.log(` - ${error}`)
}
}
}
console.log('\n📈 Statistics:')
console.log(` Files processed: ${this.results.length}`)
console.log(` Files modified: ${filesWithChanges}`)
console.log(` Total changes: ${totalChanges}`)
console.log(` Files with errors: ${filesWithErrors}`)
console.log(` Total errors: ${totalErrors}`)
if (this.options.dryRun) {
console.log('\n⚠ This was a dry run. No files were modified.')
console.log('Run without --dry-run to apply changes.')
}
if (totalErrors > 0) {
console.log('\n⚠ Some console statements need manual review.')
console.log('Search for "TODO: Migrate to structured logger" in the code.')
}
}
}
// Parse command line arguments
function parseArgs(): MigrationOptions {
const args = process.argv.slice(2)
const options: MigrationOptions = {
dryRun: false,
verbose: false
}
for (const arg of args) {
if (arg === '--dry-run') {
options.dryRun = true
} else if (arg === '--verbose' || arg === '-v') {
options.verbose = true
} else if (arg.startsWith('--file=')) {
options.targetFile = arg.split('=')[1]
}
}
return options
}
// Main execution
async function main() {
const options = parseArgs()
const migrator = new LoggerMigrator(options)
try {
await migrator.migrate()
} catch (error) {
console.error('Migration failed:', error)
process.exit(1)
}
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch(console.error)
}
export { LoggerMigrator, MigrationOptions }

View file

@ -1,269 +0,0 @@
/**
* VFS Storage Migration Script: v5.1.x v5.2.0
*
* Converts VFS files from 3-tier storage (inline/reference/chunked)
* to unified BlobStorage (content-addressable with deduplication)
*
* Usage:
* import { migrateVFSStorage } from './scripts/migrate-vfs-storage'
* await migrateVFSStorage(brain)
*/
import { Brainy } from '../src/brainy.js'
import { VFSMetadata } from '../src/vfs/types.js'
export interface MigrationStats {
filesProcessed: number
inlineMigrated: number
referenceMigrated: number
chunkedMigrated: number
alreadyMigrated: number
errors: number
deduplicated: number
totalSizeBefore: number
totalSizeAfter: number
durationMs: number
}
export async function migrateVFSStorage(
brain: Brainy,
options: {
dryRun?: boolean
verbose?: boolean
batchSize?: number
} = {}
): Promise<MigrationStats> {
const { dryRun = false, verbose = false, batchSize = 100 } = options
const stats: MigrationStats = {
filesProcessed: 0,
inlineMigrated: 0,
referenceMigrated: 0,
chunkedMigrated: 0,
alreadyMigrated: 0,
errors: 0,
deduplicated: 0,
totalSizeBefore: 0,
totalSizeAfter: 0,
durationMs: 0
}
const startTime = Date.now()
if (verbose) {
console.log('🔄 Starting VFS storage migration (v5.1.x → v5.2.0)')
if (dryRun) console.log(' DRY RUN: No changes will be made')
}
try {
// Find all VFS file entities
const files = await brain.find({
where: { 'metadata.vfsType': 'file' },
limit: 100000 // Large limit to get all files
})
if (verbose) {
console.log(`📁 Found ${files.length} VFS files to process`)
}
// Process in batches
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize)
await Promise.all(batch.map(async (file) => {
try {
stats.filesProcessed++
const metadata = file.metadata as VFSMetadata
const storage = metadata.storage
// Already migrated?
if (storage?.type === 'blob') {
stats.alreadyMigrated++
if (verbose && stats.filesProcessed % 100 === 0) {
console.log(`${metadata.path} (already migrated)`)
}
return
}
let buffer: Buffer | null = null
let sizeBefore = 0
// Migrate based on old storage type
if (!storage || storage.type === 'inline') {
// Inline storage: content in metadata.rawData
if (metadata.rawData) {
buffer = Buffer.from(metadata.rawData, 'base64')
sizeBefore = buffer.length
stats.inlineMigrated++
if (verbose && stats.filesProcessed % 100 === 0) {
console.log(`${metadata.path} (inline, ${sizeBefore} bytes)`)
}
}
} else if (storage.type === 'reference') {
// Reference storage: content stored as separate entity
if (storage.key) {
const contentEntity = await brain.get(storage.key)
if (contentEntity && contentEntity.data) {
buffer = Buffer.isBuffer(contentEntity.data)
? contentEntity.data
: Buffer.from(contentEntity.data as string)
sizeBefore = buffer.length
stats.referenceMigrated++
if (verbose && stats.filesProcessed % 100 === 0) {
console.log(`${metadata.path} (reference, ${sizeBefore} bytes)`)
}
// Delete old reference entity (unless dry run)
if (!dryRun) {
await brain.delete(storage.key)
}
}
}
} else if (storage.type === 'chunked') {
// Chunked storage: content split across multiple entities
if (storage.chunks && storage.chunks.length > 0) {
const chunkBuffers = await Promise.all(
storage.chunks.map(async (chunkId) => {
const chunkEntity = await brain.get(chunkId)
if (chunkEntity && chunkEntity.data) {
return Buffer.isBuffer(chunkEntity.data)
? chunkEntity.data
: Buffer.from(chunkEntity.data as string)
}
return Buffer.alloc(0)
})
)
buffer = Buffer.concat(chunkBuffers)
sizeBefore = buffer.length
stats.chunkedMigrated++
if (verbose && stats.filesProcessed % 100 === 0) {
console.log(`${metadata.path} (chunked, ${storage.chunks.length} chunks, ${sizeBefore} bytes)`)
}
// Delete old chunk entities (unless dry run)
if (!dryRun) {
await Promise.all(storage.chunks.map(id => brain.delete(id)))
}
}
}
// Store in BlobStorage
if (buffer) {
stats.totalSizeBefore += sizeBefore
if (!dryRun) {
// Write to BlobStorage (content-addressable)
const blobHash = await brain.vfs['blobStorage'].write(buffer)
// Check if deduplicated
const blobMeta = await brain.vfs['blobStorage'].getMetadata(blobHash)
if (blobMeta && blobMeta.refCount > 1) {
stats.deduplicated++
}
// Update VFS metadata
await brain.update(file.id, {
metadata: {
...metadata,
storage: {
type: 'blob',
hash: blobHash,
size: buffer.length,
compressed: blobMeta?.compressed
}
}
})
stats.totalSizeAfter += blobMeta?.compressedSize || buffer.length
} else {
// Dry run: just count sizes
stats.totalSizeAfter += buffer.length
}
}
} catch (error) {
stats.errors++
if (verbose) {
console.error(` ✗ Error migrating ${file.metadata?.path}: ${error.message}`)
}
}
}))
if (verbose && i + batchSize < files.length) {
const progress = Math.round(((i + batchSize) / files.length) * 100)
console.log(` Progress: ${progress}% (${i + batchSize}/${files.length})`)
}
}
stats.durationMs = Date.now() - startTime
if (verbose) {
console.log('\n✅ Migration complete!\n')
console.log('📊 Statistics:')
console.log(` Files processed: ${stats.filesProcessed}`)
console.log(` Inline migrated: ${stats.inlineMigrated}`)
console.log(` Reference migrated: ${stats.referenceMigrated}`)
console.log(` Chunked migrated: ${stats.chunkedMigrated}`)
console.log(` Already migrated: ${stats.alreadyMigrated}`)
console.log(` Deduplicated: ${stats.deduplicated}`)
console.log(` Errors: ${stats.errors}`)
console.log(` Size before: ${formatBytes(stats.totalSizeBefore)}`)
console.log(` Size after: ${formatBytes(stats.totalSizeAfter)}`)
console.log(` Space saved: ${formatBytes(stats.totalSizeBefore - stats.totalSizeAfter)}`)
console.log(` Duration: ${stats.durationMs}ms`)
if (dryRun) {
console.log('\n⚠ DRY RUN: No changes were made')
}
}
return stats
} catch (error) {
if (verbose) {
console.error('❌ Migration failed:', error)
}
throw error
}
}
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]
}
/**
* Auto-detect and migrate if needed
* Called automatically on brain.init() if old format detected
*/
export async function autoMigrateIfNeeded(brain: Brainy): Promise<boolean> {
try {
// Check for old format files
const oldFormatFiles = await brain.find({
where: {
'metadata.vfsType': 'file',
'metadata.storage.type': { $in: ['inline', 'reference', 'chunked'] }
},
limit: 1
})
if (oldFormatFiles.length > 0) {
console.log('🔄 Detected v5.1.x VFS storage format. Auto-migrating to v5.2.0...')
await migrateVFSStorage(brain, { verbose: true })
return true
}
return false
} catch (error) {
console.error('❌ Auto-migration failed:', error)
return false
}
}

View file

@ -1,88 +0,0 @@
#!/bin/bash
# Setup GitHub branch with extracted model files for direct serving
# This creates a 'models' branch with uncompressed files that transformers.js can use directly
set -e
echo "🚀 Setting up GitHub models branch for reliable model serving..."
# Create a temporary directory for our work
TEMP_DIR=$(mktemp -d)
cd "$TEMP_DIR"
echo "📥 Downloading model tar.gz from release..."
curl -sL https://github.com/soulcraftlabs/brainy/releases/download/models-v1/all-MiniLM-L6-v2.tar.gz -o models.tar.gz
echo "📦 Extracting models..."
tar -xzf models.tar.gz
echo "🔧 Setting up models branch..."
git clone https://github.com/soulcraftlabs/brainy.git --depth 1 -b main repo
cd repo
# Create orphan branch for models (no history needed)
git checkout --orphan models
git rm -rf . 2>/dev/null || true
# Create the proper directory structure for transformers.js
mkdir -p Xenova/all-MiniLM-L6-v2/onnx
# Copy model files with correct structure
cp ../all-MiniLM-L6-v2/config.json Xenova/all-MiniLM-L6-v2/
cp ../all-MiniLM-L6-v2/tokenizer.json Xenova/all-MiniLM-L6-v2/
cp ../all-MiniLM-L6-v2/tokenizer_config.json Xenova/all-MiniLM-L6-v2/
cp ../all-MiniLM-L6-v2/onnx/model.onnx Xenova/all-MiniLM-L6-v2/onnx/
cp ../all-MiniLM-L6-v2/onnx/model_quantized.onnx Xenova/all-MiniLM-L6-v2/onnx/ 2>/dev/null || true
# Add README explaining this branch
cat > README.md << 'EOF'
# Brainy Model Files
This branch contains extracted model files for direct serving via raw.githubusercontent.com
## Structure
```
Xenova/
all-MiniLM-L6-v2/
config.json
tokenizer.json
tokenizer_config.json
onnx/
model.onnx
model_quantized.onnx
```
## Usage
These files are automatically loaded by Brainy when models are needed.
The URL pattern is:
`https://raw.githubusercontent.com/soulcraftlabs/brainy/models/Xenova/all-MiniLM-L6-v2/{file}`
## DO NOT EDIT
This branch is managed automatically. Do not edit files directly.
EOF
# Create .gitattributes for LFS if needed (for large model files)
cat > .gitattributes << 'EOF'
*.onnx filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
*.safetensors filter=lfs diff=lfs merge=lfs -text
EOF
echo "📝 Creating commit..."
git add .
git commit -m "feat: extracted model files for direct GitHub serving
- Xenova/all-MiniLM-L6-v2 model files
- Proper directory structure for transformers.js
- Direct serving via raw.githubusercontent.com"
echo "✅ Models branch ready!"
echo ""
echo "To push this branch to GitHub, run:"
echo " cd $TEMP_DIR/repo"
echo " git push origin models"
echo ""
echo "Once pushed, models will be available at:"
echo " https://raw.githubusercontent.com/soulcraftlabs/brainy/models/Xenova/all-MiniLM-L6-v2/config.json"
echo " https://raw.githubusercontent.com/soulcraftlabs/brainy/models/Xenova/all-MiniLM-L6-v2/tokenizer.json"
echo " etc..."

View file

@ -1,28 +0,0 @@
#!/bin/bash
# Run tests with adequate memory for transformer models
echo "🧠 Running Brainy tests with 8GB heap allocation"
echo "This is required for the transformer model (ONNX runtime)"
echo "================================================"
# Set memory allocation
export NODE_OPTIONS='--max-old-space-size=8192'
# Run tests based on argument
if [ "$1" = "single" ]; then
echo "Running tests sequentially (memory-safe)..."
npx vitest run --pool=forks --poolOptions.forks.maxForks=1 --reporter=dot
elif [ "$1" = "quick" ]; then
echo "Running quick test..."
node test-quick.js
elif [ "$1" = "core" ]; then
echo "Running core tests only..."
npx vitest run tests/core.test.ts --reporter=verbose
else
echo "Running full test suite..."
echo "Note: This requires 8GB+ RAM available"
npm test
fi
echo ""
echo "Test complete. Memory was allocated at 8GB for ONNX runtime."

View file

@ -1,63 +0,0 @@
#!/bin/bash
# Script to update all augmentations with metadata declarations
echo "📝 Updating augmentations with metadata declarations..."
# Category 1: No metadata access ('none')
echo "🚫 Updating 'none' metadata augmentations..."
# RequestDeduplicatorAugmentation
sed -i "/export class RequestDeduplicatorAugmentation extends BaseAugmentation {/a\\
readonly metadata = 'none' as const // Doesn't access metadata" \
src/augmentations/requestDeduplicatorAugmentation.ts
# ConnectionPoolAugmentation
sed -i "/export class ConnectionPoolAugmentation extends BaseAugmentation {/a\\
readonly metadata = 'none' as const // Doesn't access metadata" \
src/augmentations/connectionPoolAugmentation.ts
# StorageAugmentation (abstract class)
sed -i "/export abstract class StorageAugmentation extends BaseAugmentation/a\\
readonly metadata = 'none' as const // Storage doesn't directly access metadata" \
src/augmentations/storageAugmentation.ts
# Category 2: Read-only access ('readonly')
echo "👁 Updating 'readonly' metadata augmentations..."
# WALAugmentation
sed -i "/export class WALAugmentation extends BaseAugmentation {/a\\
readonly metadata = 'readonly' as const // Reads metadata for logging" \
src/augmentations/walAugmentation.ts
# IndexAugmentation
sed -i "/export class IndexAugmentation extends BaseAugmentation {/a\\
readonly metadata = 'readonly' as const // Reads metadata to build indexes" \
src/augmentations/indexAugmentation.ts
# MonitoringAugmentation
sed -i "/export class MonitoringAugmentation extends BaseAugmentation {/a\\
readonly metadata = 'readonly' as const // Reads metadata for monitoring" \
src/augmentations/monitoringAugmentation.ts
# MetricsAugmentation
sed -i "/export class MetricsAugmentation extends BaseAugmentation {/a\\
readonly metadata = 'readonly' as const // Reads metadata for metrics" \
src/augmentations/metricsAugmentation.ts
# BatchProcessingAugmentation
sed -i "/export class BatchProcessingAugmentation extends BaseAugmentation {/a\\
readonly metadata = 'readonly' as const // Reads metadata for batching decisions" \
src/augmentations/batchProcessingAugmentation.ts
# EntityRegistryAugmentation
sed -i "/export class EntityRegistryAugmentation extends BaseAugmentation {/a\\
readonly metadata = 'readonly' as const // Reads metadata to register entities" \
src/augmentations/entityRegistryAugmentation.ts
# AutoRegisterEntitiesAugmentation
sed -i "/export class AutoRegisterEntitiesAugmentation extends BaseAugmentation {/a\\
readonly metadata = 'readonly' as const // Reads metadata for auto-registration" \
src/augmentations/entityRegistryAugmentation.ts
echo "✅ Done updating augmentations!"

View file

@ -312,7 +312,7 @@ export class AggregationIndex {
*/ */
async init(): Promise<void> { async init(): Promise<void> {
// Load persisted definitions // Load persisted definitions
const savedDefs = await this.storage.getMetadata(DEFINITIONS_KEY) as any const savedDefs = await this.storage.getMetadata(DEFINITIONS_KEY)
if (savedDefs && typeof savedDefs === 'object' && savedDefs.definitions) { if (savedDefs && typeof savedDefs === 'object' && savedDefs.definitions) {
const defs = savedDefs.definitions as Array<AggregateDefinition & { _hash?: string }> const defs = savedDefs.definitions as Array<AggregateDefinition & { _hash?: string }>
@ -322,7 +322,7 @@ export class AggregationIndex {
const savedHash = def._hash || '' const savedHash = def._hash || ''
// Load persisted state // Load persisted state
const stateData = await this.storage.getMetadata(`${STATE_KEY_PREFIX}${def.name}__`) as any const stateData = await this.storage.getMetadata(`${STATE_KEY_PREFIX}${def.name}__`)
if (stateData && stateData.groups && savedHash === currentHash) { if (stateData && stateData.groups && savedHash === currentHash) {
// Definition unchanged — load state // Definition unchanged — load state
const groupMap = new Map<string, AggregateGroupState>() const groupMap = new Map<string, AggregateGroupState>()
@ -349,11 +349,13 @@ export class AggregationIndex {
// Restore native provider state from persistence // Restore native provider state from persistence
if (this.nativeProvider?.restoreState) { if (this.nativeProvider?.restoreState) {
const nativeState = await this.storage.getMetadata('__aggregation_native_state__') as any const nativeState = await this.storage.getMetadata('__aggregation_native_state__')
if (nativeState && typeof nativeState === 'string') { if (nativeState && typeof nativeState === 'string') {
this.nativeProvider.restoreState(nativeState) this.nativeProvider.restoreState(nativeState)
} else if (nativeState && typeof nativeState === 'object' && nativeState.data) { } else if (nativeState && typeof nativeState === 'object' && nativeState.data) {
this.nativeProvider.restoreState(nativeState.data) // flush() persists `{ data: serializeState() }`, so `data` is the
// provider's serialized state string.
this.nativeProvider.restoreState(nativeState.data as string)
} }
} }
} }
@ -367,7 +369,7 @@ export class AggregationIndex {
...def, ...def,
_hash: this.definitionHashes.get(def.name) _hash: this.definitionHashes.get(def.name)
})) }))
await this.storage.saveMetadata(DEFINITIONS_KEY, { definitions: defsToSave } as any) await this.storage.saveMetadata(DEFINITIONS_KEY, { definitions: defsToSave })
// Persist dirty states // Persist dirty states
for (const name of this.dirty) { for (const name of this.dirty) {
@ -376,7 +378,7 @@ export class AggregationIndex {
const groups = Array.from(stateMap.values()) const groups = Array.from(stateMap.values())
await this.storage.saveMetadata( await this.storage.saveMetadata(
`${STATE_KEY_PREFIX}${name}__`, `${STATE_KEY_PREFIX}${name}__`,
{ groups } as any { groups }
) )
} }
} }
@ -386,7 +388,7 @@ export class AggregationIndex {
const nativeState = this.nativeProvider.serializeState() const nativeState = this.nativeProvider.serializeState()
await this.storage.saveMetadata( await this.storage.saveMetadata(
'__aggregation_native_state__', '__aggregation_native_state__',
{ data: nativeState } as any { data: nativeState }
) )
} }
@ -613,7 +615,7 @@ export class AggregationIndex {
// Apply where filter on group keys // Apply where filter on group keys
if (params.where && Object.keys(params.where).length > 0) { if (params.where && Object.keys(params.where).length > 0) {
if (!matchesMetadataFilter(group.groupKey as any, params.where)) continue if (!matchesMetadataFilter(group.groupKey, params.where)) continue
} }
// Compute result metrics from running state // Compute result metrics from running state
@ -664,7 +666,7 @@ export class AggregationIndex {
// HAVING: filter groups by computed metric values (post-compute, O(groups), before // HAVING: filter groups by computed metric values (post-compute, O(groups), before
// sort/pagination). Reuses the where-operator engine over metrics + `count`. // sort/pagination). Reuses the where-operator engine over metrics + `count`.
if (params.having && Object.keys(params.having).length > 0) { if (params.having && Object.keys(params.having).length > 0) {
if (!matchesMetadataFilter({ ...metrics, count: totalCount } as any, params.having)) continue if (!matchesMetadataFilter({ ...metrics, count: totalCount }, params.having)) continue
} }
results.push({ results.push({

View file

@ -1,262 +0,0 @@
/**
* Configuration API for Brainy 3.0
* Provides configuration storage with optional encryption
*/
import { SecurityAPI } from './SecurityAPI.js'
import { StorageAdapter } from '../coreTypes.js'
export interface ConfigOptions {
encrypt?: boolean
decrypt?: boolean
}
export interface ConfigEntry {
key: string
value: any
encrypted: boolean
createdAt: number
updatedAt: number
}
export class ConfigAPI {
private security: SecurityAPI
private configCache: Map<string, ConfigEntry> = new Map()
private CONFIG_NOUN_PREFIX = '_config_'
constructor(private storage: StorageAdapter) {
this.security = new SecurityAPI()
}
/**
* Set a configuration value with optional encryption
*/
async set(params: {
key: string
value: any
encrypt?: boolean
}): Promise<void> {
const { key, value, encrypt = false } = params
// Serialize and optionally encrypt the value
let storedValue: any = value
if (typeof value !== 'string') {
storedValue = JSON.stringify(value)
}
if (encrypt) {
storedValue = await this.security.encrypt(storedValue)
}
// Create config entry
const entry: ConfigEntry = {
key,
value: storedValue,
encrypted: encrypt,
createdAt: this.configCache.get(key)?.createdAt || Date.now(),
updatedAt: Date.now()
}
// Store in cache
this.configCache.set(key, entry)
// Persist to storage as NounMetadata
const configId = this.CONFIG_NOUN_PREFIX + key
const nounMetadata = {
noun: 'config' as any,
...entry,
service: 'config',
createdAt: entry.createdAt,
updatedAt: entry.updatedAt
}
await this.storage.saveNounMetadata(configId, nounMetadata)
}
/**
* Get a configuration value with optional decryption
*/
async get(params: {
key: string
decrypt?: boolean
defaultValue?: any
}): Promise<any> {
const { key, decrypt, defaultValue } = params
// Check cache first
let entry = this.configCache.get(key)
// If not in cache, load from storage
if (!entry) {
const configId = this.CONFIG_NOUN_PREFIX + key
const metadata = await this.storage.getNounMetadata(configId)
if (!metadata) {
return defaultValue
}
// Extract ConfigEntry from NounMetadata
const createdAtVal = typeof metadata.createdAt === 'object' && metadata.createdAt !== null && 'seconds' in metadata.createdAt
? metadata.createdAt.seconds * 1000 + Math.floor((metadata.createdAt.nanoseconds || 0) / 1000000)
: ((metadata.createdAt as unknown as number) || Date.now())
const updatedAtVal = typeof metadata.updatedAt === 'object' && metadata.updatedAt !== null && 'seconds' in metadata.updatedAt
? metadata.updatedAt.seconds * 1000 + Math.floor((metadata.updatedAt.nanoseconds || 0) / 1000000)
: ((metadata.updatedAt as unknown as number) || createdAtVal)
entry = {
key: metadata.key as string,
value: metadata.value,
encrypted: metadata.encrypted as boolean,
createdAt: createdAtVal,
updatedAt: updatedAtVal
}
this.configCache.set(key, entry)
}
let value = entry.value
// Decrypt if needed
const shouldDecrypt = decrypt !== undefined ? decrypt : entry.encrypted
if (shouldDecrypt && entry.encrypted && typeof value === 'string') {
value = await this.security.decrypt(value)
}
// Try to parse JSON if it looks like JSON
if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {
try {
value = JSON.parse(value)
} catch {
// Not JSON, return as string
}
}
return value
}
/**
* Delete a configuration value
*/
async delete(key: string): Promise<void> {
// Remove from cache
this.configCache.delete(key)
// Remove from storage
const configId = this.CONFIG_NOUN_PREFIX + key
await this.storage.deleteNounMetadata(configId)
}
/**
* List all configuration keys
*/
async list(): Promise<string[]> {
// Get all nouns and filter for config entries
const result = await this.storage.getNouns({ pagination: { limit: 10000 } })
const configKeys: string[] = []
for (const noun of result.items) {
if (noun.id.startsWith(this.CONFIG_NOUN_PREFIX)) {
configKeys.push(noun.id.substring(this.CONFIG_NOUN_PREFIX.length))
}
}
return configKeys
}
/**
* Check if a configuration key exists
*/
async has(key: string): Promise<boolean> {
if (this.configCache.has(key)) {
return true
}
const configId = this.CONFIG_NOUN_PREFIX + key
const metadata = await this.storage.getNounMetadata(configId)
return metadata !== null && metadata !== undefined
}
/**
* Clear all configuration
*/
async clear(): Promise<void> {
// Clear cache
this.configCache.clear()
// Clear from storage
const keys = await this.list()
for (const key of keys) {
await this.delete(key)
}
}
/**
* Export all configuration
*/
async export(): Promise<Record<string, ConfigEntry>> {
const keys = await this.list()
const config: Record<string, ConfigEntry> = {}
for (const key of keys) {
const entry = await this.getEntry(key)
if (entry) {
config[key] = entry
}
}
return config
}
/**
* Import configuration
*/
async import(config: Record<string, ConfigEntry>): Promise<void> {
for (const [key, entry] of Object.entries(config)) {
this.configCache.set(key, entry)
const configId = this.CONFIG_NOUN_PREFIX + key
// Convert ConfigEntry to NounMetadata
const nounMetadata = {
noun: 'config' as any,
...entry,
service: 'config',
createdAt: entry.createdAt,
updatedAt: entry.updatedAt
}
await this.storage.saveNounMetadata(configId, nounMetadata)
}
}
/**
* Get raw config entry (without decryption)
*/
private async getEntry(key: string): Promise<ConfigEntry | null> {
if (this.configCache.has(key)) {
return this.configCache.get(key)!
}
const configId = this.CONFIG_NOUN_PREFIX + key
const metadata = await this.storage.getNounMetadata(configId)
if (!metadata) {
return null
}
// Extract ConfigEntry from NounMetadata
const createdAtVal = typeof metadata.createdAt === 'object' && metadata.createdAt !== null && 'seconds' in metadata.createdAt
? metadata.createdAt.seconds * 1000 + Math.floor((metadata.createdAt.nanoseconds || 0) / 1000000)
: ((metadata.createdAt as unknown as number) || Date.now())
const updatedAtVal = typeof metadata.updatedAt === 'object' && metadata.updatedAt !== null && 'seconds' in metadata.updatedAt
? metadata.updatedAt.seconds * 1000 + Math.floor((metadata.updatedAt.nanoseconds || 0) / 1000000)
: ((metadata.updatedAt as unknown as number) || createdAtVal)
const entry: ConfigEntry = {
key: metadata.key as string,
value: metadata.value,
encrypted: metadata.encrypted as boolean,
createdAt: createdAtVal,
updatedAt: updatedAtVal
}
this.configCache.set(key, entry)
return entry
}
}

View file

@ -1,163 +0,0 @@
/**
* Security API for Brainy 3.0
* Provides encryption, decryption, hashing, and secure storage
*/
export class SecurityAPI {
private encryptionKey?: Uint8Array
constructor(private config?: { encryptionKey?: string }) {
if (config?.encryptionKey) {
// Use provided key (must be 32 bytes hex string)
this.encryptionKey = this.hexToBytes(config.encryptionKey)
}
}
/**
* Encrypt data using AES-256-CBC
*/
async encrypt(data: string): Promise<string> {
const crypto = await import('../universal/crypto.js')
// Generate or use existing key
const key = this.encryptionKey || crypto.randomBytes(32)
const iv = crypto.randomBytes(16)
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv)
let encrypted = cipher.update(data, 'utf8', 'hex')
encrypted += cipher.final('hex')
// Package encrypted data with metadata
// In production, store keys separately in a key management service
return JSON.stringify({
encrypted,
key: this.bytesToHex(key),
iv: this.bytesToHex(iv),
algorithm: 'aes-256-cbc',
timestamp: Date.now()
})
}
/**
* Decrypt data encrypted with encrypt()
*/
async decrypt(encryptedData: string): Promise<string> {
const crypto = await import('../universal/crypto.js')
try {
const parsed = JSON.parse(encryptedData)
const { encrypted, key: keyHex, iv: ivHex, algorithm } = parsed
if (algorithm && algorithm !== 'aes-256-cbc') {
throw new Error(`Unsupported encryption algorithm: ${algorithm}`)
}
const key = this.hexToBytes(keyHex)
const iv = this.hexToBytes(ivHex)
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
} catch (error) {
throw new Error(`Decryption failed: ${(error as Error).message}`)
}
}
/**
* Create a one-way hash of data (for passwords, etc)
*/
async hash(data: string, algorithm: 'sha256' | 'sha512' = 'sha256'): Promise<string> {
const crypto = await import('../universal/crypto.js')
const hash = crypto.createHash(algorithm)
hash.update(data)
return hash.digest('hex')
}
/**
* Compare data with a hash (for password verification)
*/
async compare(data: string, hash: string, algorithm: 'sha256' | 'sha512' = 'sha256'): Promise<boolean> {
const dataHash = await this.hash(data, algorithm)
return this.constantTimeCompare(dataHash, hash)
}
/**
* Generate a secure random token
*/
async generateToken(bytes: number = 32): Promise<string> {
const crypto = await import('../universal/crypto.js')
const buffer = crypto.randomBytes(bytes)
return this.bytesToHex(buffer)
}
/**
* Derive a key from a password using PBKDF2
* Note: Simplified version using hash instead of PBKDF2 which may not be available
*/
async deriveKey(password: string, salt?: string, iterations: number = 100000): Promise<{
key: string
salt: string
}> {
const crypto = await import('../universal/crypto.js')
const actualSalt = salt || this.bytesToHex(crypto.randomBytes(32))
// Simplified key derivation using repeated hashing
// In production, use a proper PBKDF2 implementation
let derived = password + actualSalt
for (let i = 0; i < Math.min(iterations, 1000); i++) {
const hash = crypto.createHash('sha256')
hash.update(derived)
derived = hash.digest('hex')
}
return {
key: derived,
salt: actualSalt
}
}
/**
* Sign data with HMAC
*/
async sign(data: string, secret?: string): Promise<string> {
const crypto = await import('../universal/crypto.js')
const actualSecret = secret || (await this.generateToken())
const hmac = crypto.createHmac('sha256', actualSecret)
hmac.update(data)
return hmac.digest('hex')
}
/**
* Verify HMAC signature
*/
async verify(data: string, signature: string, secret: string): Promise<boolean> {
const expectedSignature = await this.sign(data, secret)
return this.constantTimeCompare(signature, expectedSignature)
}
// Helper methods
private hexToBytes(hex: string): Uint8Array {
const matches = hex.match(/.{1,2}/g)
if (!matches) throw new Error('Invalid hex string')
return new Uint8Array(matches.map(byte => parseInt(byte, 16)))
}
private bytesToHex(bytes: Uint8Array | Buffer): string {
return Array.from(bytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('')
}
private constantTimeCompare(a: string, b: string): boolean {
if (a.length !== b.length) return false
let result = 0
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i)
}
return result === 0
}
}

View file

@ -1,930 +0,0 @@
/**
* Universal Neural Import API
*
* ALWAYS uses neural matching to map ANY data to our strict NounTypes and VerbTypes
* Never falls back to rules - neural matching is MANDATORY
*
* Handles:
* - Strings (text, JSON, CSV, YAML, Markdown)
* - Files (local paths, any format) - uses MimeTypeDetector for 2000+ types
* - URLs (web pages, APIs, documents)
* - Objects (structured data)
* - Binary data (images, PDFs via extraction)
*/
import { NounType, VerbType } from '../types/graphTypes.js'
import { Vector } from '../coreTypes.js'
import type { Brainy } from '../brainy.js'
import type { Entity, Relation } from '../types/brainy.types.js'
import { NeuralImportAugmentation } from '../neural/neuralImportAugmentation.js'
import { mimeDetector } from '../vfs/MimeTypeDetector.js'
export interface ImportSource {
type: 'string' | 'file' | 'url' | 'object' | 'binary'
data: any
format?: string // Optional hint about format
metadata?: any // Additional context
}
export interface NeuralImportResult {
entities: Array<{
id: string
type: NounType
data: any
vector: Vector
confidence: number
metadata: any
}>
relationships: Array<{
id: string
from: string
to: string
type: VerbType
weight: number
confidence: number
metadata?: any
}>
stats: {
totalProcessed: number
entitiesCreated: number
relationshipsCreated: number
averageConfidence: number
processingTimeMs: number
}
}
export interface NeuralImportProgress {
phase: 'extracting' | 'storing-entities' | 'storing-relationships' | 'complete'
message: string
current: number
total: number
entities?: number
relationships?: number
}
export class UniversalImportAPI {
private brain: Brainy<any>
private neuralImport: NeuralImportAugmentation
private embedCache = new Map<string, Vector>()
constructor(brain: Brainy<any>) {
this.brain = brain
this.neuralImport = new NeuralImportAugmentation({
confidenceThreshold: 0.0, // Accept ALL confidence levels - never reject
enableWeights: true,
skipDuplicates: false // Process everything
})
}
/**
* Initialize the neural import system
*/
async init(): Promise<void> {
// Neural import initializes itself
}
/**
* Universal import - handles ANY data source
* ALWAYS uses neural matching, NEVER falls back
*/
async import(
source: ImportSource | string | any,
options?: { onProgress?: (progress: NeuralImportProgress) => void }
): Promise<NeuralImportResult> {
const startTime = Date.now()
// Normalize source
const normalizedSource = this.normalizeSource(source)
options?.onProgress?.({
phase: 'extracting',
message: 'Extracting data from source...',
current: 0,
total: 0
})
// Extract data based on source type
const extractedData = await this.extractData(normalizedSource)
// Neural processing - MANDATORY
const neuralResults = await this.neuralProcess(extractedData)
// Store in brain
const result = await this.storeInBrain(neuralResults, options?.onProgress)
result.stats.processingTimeMs = Date.now() - startTime
options?.onProgress?.({
phase: 'complete',
message: 'Import complete',
current: result.stats.entitiesCreated + result.stats.relationshipsCreated,
total: result.stats.totalProcessed,
entities: result.stats.entitiesCreated,
relationships: result.stats.relationshipsCreated
})
return result
}
/**
* Import from URL - fetches and processes
*/
async importFromURL(url: string): Promise<NeuralImportResult> {
const response = await fetch(url)
const contentType = response.headers.get('content-type') || 'text/plain'
let data: any
if (contentType.includes('json')) {
data = await response.json()
} else if (contentType.includes('text') || contentType.includes('html')) {
data = await response.text()
} else {
// Binary data
const buffer = await response.arrayBuffer()
data = new Uint8Array(buffer)
}
return this.import({
type: 'url',
data,
format: contentType,
metadata: { url, fetchedAt: Date.now() }
})
}
/**
* Import from file - reads and processes
* Note: In browser environment, use File API instead
*
* Uses MimeTypeDetector for comprehensive format detection (2000+ types)
*/
async importFromFile(filePath: string): Promise<NeuralImportResult> {
// Read the actual file content
const { readFileSync } = await import('node:fs')
// Use MimeTypeDetector for comprehensive format detection
const mimeType = mimeDetector.detectMimeType(filePath)
const ext = filePath.split('.').pop()?.toLowerCase() || 'txt'
try {
const fileContent = readFileSync(filePath, 'utf-8')
return this.import({
type: 'file',
data: fileContent, // Actual file content
format: ext, // Keep ext for backward compatibility
metadata: {
path: filePath,
mimeType, // Add detected MIME type
importedAt: Date.now(),
fileSize: fileContent.length
}
})
} catch (error) {
throw new Error(`Failed to read file ${filePath}: ${(error as Error).message}`)
}
}
/**
* Normalize any input to ImportSource
*/
private normalizeSource(source: any): ImportSource {
// Already normalized
if (source && typeof source === 'object' && 'type' in source && 'data' in source) {
return source as ImportSource
}
// String input
if (typeof source === 'string') {
// Check if it's a URL
if (source.startsWith('http://') || source.startsWith('https://')) {
return { type: 'url', data: source }
}
// Check if it looks like a file path
if (source.includes('/') || source.includes('\\') || source.includes('.')) {
// Assume it's a file path reference
return { type: 'file', data: source }
}
// Treat as raw string data
return { type: 'string', data: source }
}
// Object/Array input
if (typeof source === 'object') {
return { type: 'object', data: source }
}
// Default to string
return { type: 'string', data: String(source) }
}
/**
* Extract structured data from source
*/
private async extractData(source: ImportSource): Promise<any[]> {
switch (source.type) {
case 'url':
// URL is in data field, need to fetch
return this.extractFromURL(source.data)
case 'file':
// File path is in data field, need to read
return this.extractFromFile(source.data)
case 'string':
return this.extractFromString(source.data, source.format)
case 'object':
return Array.isArray(source.data) ? source.data : [source.data]
case 'binary':
return this.extractFromBinary(source.data, source.format)
default:
// Unknown type, treat as object
return [source.data]
}
}
/**
* Extract data from URL
*/
private async extractFromURL(url: string): Promise<any[]> {
const result = await this.importFromURL(url)
return result.entities.map(e => e.data)
}
/**
* Extract data from file
*/
private async extractFromFile(filePath: string): Promise<any[]> {
const result = await this.importFromFile(filePath)
return result.entities.map(e => e.data)
}
/**
* Extract data from string based on format
*/
private extractFromString(data: string, format?: string): any[] {
// Try to detect format if not provided
const detectedFormat = format || this.detectFormat(data)
switch (detectedFormat) {
case 'json':
try {
const parsed = JSON.parse(data)
return Array.isArray(parsed) ? parsed : [parsed]
} catch {
// Not valid JSON, treat as text
return this.extractFromText(data)
}
case 'csv':
return this.parseCSV(data)
case 'yaml':
case 'yml':
return this.parseYAML(data)
case 'markdown':
case 'md':
return this.parseMarkdown(data)
case 'xml':
case 'html':
return this.parseHTML(data)
default:
return this.extractFromText(data)
}
}
/**
* Extract from binary data (images, PDFs, etc)
*/
private async extractFromBinary(data: Uint8Array, format?: string): Promise<any[]> {
// For now, create a single entity representing the binary data
// In production, would use OCR, image recognition, PDF extraction, etc.
return [{
type: 'binary',
format: format || 'unknown',
size: data.length,
hash: await this.hashBinary(data),
extractedAt: Date.now()
}]
}
/**
* Extract entities from plain text
*/
private extractFromText(text: string): any[] {
// Split into meaningful chunks
const chunks: any[] = []
// Split by paragraphs
const paragraphs = text.split(/\n\n+/)
for (const para of paragraphs) {
if (para.trim()) {
chunks.push({
text: para.trim(),
type: 'paragraph',
length: para.length
})
}
}
// If no paragraphs, split by sentences
if (chunks.length === 0) {
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text]
for (const sentence of sentences) {
if (sentence.trim()) {
chunks.push({
text: sentence.trim(),
type: 'sentence',
length: sentence.length
})
}
}
}
return chunks
}
/**
* Neural processing - CORE of the system
* ALWAYS uses embeddings and neural matching
*/
private async neuralProcess(data: any[]): Promise<{
entities: Map<string, any>
relationships: Map<string, any>
}> {
const entities = new Map<string, any>()
const relationships = new Map<string, any>()
for (const item of data) {
// Generate embedding for the item
const embedding = await this.generateEmbedding(item)
// Determine noun type from data
const nounType = this.inferNounType(item)
const entityId = this.generateId(item)
entities.set(entityId, {
id: entityId,
type: nounType,
data: item,
vector: embedding,
confidence: 1.0,
metadata: {
...item,
_importedAt: Date.now()
}
})
// Detect relationships using neural matching
await this.detectNeuralRelationships(item, entityId, entities, relationships)
}
return { entities, relationships }
}
/**
* Generate embedding for any data
*/
private async generateEmbedding(data: any): Promise<Vector> {
// Convert to string for embedding
const text = this.dataToText(data)
// Check cache
if (this.embedCache.has(text)) {
return this.embedCache.get(text)!
}
// Generate new embedding
const embedding = await (this.brain as any).embed(text)
// Cache it
this.embedCache.set(text, embedding)
return embedding
}
/**
* Convert any data to text for embedding
*/
private dataToText(data: any): string {
if (typeof data === 'string') return data
if (typeof data === 'object') {
// Extract meaningful text from object
const parts: string[] = []
// Priority fields
const priorityFields = ['name', 'title', 'description', 'text', 'content', 'label', 'value']
for (const field of priorityFields) {
if (data[field]) {
parts.push(String(data[field]))
}
}
// Add other fields
for (const [key, value] of Object.entries(data)) {
if (!priorityFields.includes(key) && value) {
if (typeof value === 'string' || typeof value === 'number') {
parts.push(`${key}: ${value}`)
}
}
}
return parts.join(' ')
}
return JSON.stringify(data)
}
/**
* Detect relationships using neural matching
*/
private async detectNeuralRelationships(
item: any,
sourceId: string,
entities: Map<string, any>,
relationships: Map<string, any>
): Promise<void> {
if (typeof item !== 'object') return
// Look for references to other entities
for (const [key, value] of Object.entries(item)) {
// Check if this looks like a reference
if (this.looksLikeReference(key, value)) {
const targetId = String(value)
const verbType = this.inferVerbType(key)
const relationId = `${sourceId}_${verbType}_${targetId}`
relationships.set(relationId, {
id: relationId,
from: sourceId,
to: targetId,
type: verbType,
weight: 1.0,
confidence: 1.0,
metadata: {
field: key,
_importedAt: Date.now()
}
})
}
// Handle arrays of references
if (Array.isArray(value)) {
for (const arrayItem of value) {
if (this.looksLikeReference(key, arrayItem)) {
const targetId = String(arrayItem)
const verbType = this.inferVerbType(key)
const relationId = `${sourceId}_${verbType}_${targetId}`
relationships.set(relationId, {
id: relationId,
from: sourceId,
to: targetId,
type: verbType,
weight: 1.0,
confidence: 1.0,
metadata: {
field: key,
array: true,
_importedAt: Date.now()
}
})
}
}
}
}
}
/**
* Check if a field looks like a reference
*/
private looksLikeReference(key: string, value: any): boolean {
// Field name patterns that suggest references
const refPatterns = [
/[Ii]d$/, // ends with Id or id
/_id$/, // ends with _id
/^parent/i, // starts with parent
/^child/i, // starts with child
/^related/i, // starts with related
/^ref/i, // starts with ref
/^link/i, // starts with link
/^target/i, // starts with target
/^source/i, // starts with source
]
// Check if field name matches patterns
const fieldLooksLikeRef = refPatterns.some(pattern => pattern.test(key))
// Check if value looks like an ID
const valueLooksLikeId = (
typeof value === 'string' ||
typeof value === 'number'
) && String(value).length > 0
return fieldLooksLikeRef && valueLooksLikeId
}
/**
* Infer noun type from object structure using field heuristics
*/
private inferNounType(obj: any): NounType {
if (typeof obj !== 'object' || obj === null) return NounType.Thing
// Check for explicit type field
if (obj.type && typeof obj.type === 'string') {
const normalized = obj.type.charAt(0).toUpperCase() + obj.type.slice(1)
if (Object.values(NounType).includes(normalized as NounType)) {
return normalized as NounType
}
}
// Person heuristics
if (obj.email || obj.firstName || obj.lastName || obj.username || obj.age) {
return NounType.Person
}
// Organization heuristics
if (obj.companyName || obj.organizationId || obj.employees || obj.industry) {
return NounType.Organization
}
// Location heuristics
if (obj.latitude || obj.longitude || obj.address || obj.city || obj.country) {
return NounType.Location
}
// Document heuristics
if ((obj.content && (obj.title || obj.author)) || obj.documentType || obj.pages) {
return NounType.Document
}
// Event heuristics
if (obj.startTime || obj.endTime || obj.date || obj.eventType || obj.attendees) {
return NounType.Event
}
// Product heuristics
if (obj.price || obj.sku || obj.inventory || obj.productId) {
return NounType.Product
}
// Task heuristics
if ((obj.status && (obj.assignee || obj.dueDate)) || obj.priority || obj.completed !== undefined) {
return NounType.Task
}
// Dataset heuristics
if (Array.isArray(obj.data) || obj.rows || obj.columns || obj.schema) {
return NounType.Dataset
}
return NounType.Thing
}
/**
* Infer verb type from field name using common patterns
*/
private inferVerbType(fieldName: string): VerbType {
const field = fieldName.toLowerCase()
if (field.includes('parent') || field.includes('child') || field.includes('contain')) {
return VerbType.Contains
}
if (field.includes('owner') || field.includes('created') || field.includes('author')) {
return VerbType.Creates
}
if (field.includes('member') || field.includes('belong')) {
return VerbType.MemberOf
}
if (field.includes('depend') || field.includes('require')) {
return VerbType.DependsOn
}
if (field.includes('ref') || field.includes('link') || field.includes('source')) {
return VerbType.References
}
return VerbType.RelatedTo
}
/**
* Store processed data in brain
*/
private async storeInBrain(
neuralResults: {
entities: Map<string, any>
relationships: Map<string, any>
},
onProgress?: (progress: NeuralImportProgress) => void
): Promise<NeuralImportResult> {
const result: NeuralImportResult = {
entities: [],
relationships: [],
stats: {
totalProcessed: neuralResults.entities.size + neuralResults.relationships.size,
entitiesCreated: 0,
relationshipsCreated: 0,
averageConfidence: 0,
processingTimeMs: 0
}
}
let totalConfidence = 0
// Store entities
onProgress?.({
phase: 'storing-entities',
message: 'Storing entities...',
current: 0,
total: neuralResults.entities.size
})
let entitiesProcessed = 0
for (const entity of neuralResults.entities.values()) {
// Subtype precedence: extractor-set → `'extracted'` (this is the universal
// neural-extraction path, so `'extracted'` is the honest provenance label).
// Added 7.30.1 so enforcement consumers don't get rejected on auto-extraction.
const id = await this.brain.add({
data: entity.data,
type: entity.type,
subtype: (entity as any).subtype ?? 'extracted',
metadata: entity.metadata,
vector: entity.vector
})
// Update entity ID for relationship mapping
entity.id = id
result.entities.push({
...entity,
id
})
result.stats.entitiesCreated++
totalConfidence += entity.confidence
entitiesProcessed++
// Report progress periodically
if (entitiesProcessed % 10 === 0 || entitiesProcessed === neuralResults.entities.size) {
onProgress?.({
phase: 'storing-entities',
message: `Storing entities: ${entitiesProcessed}/${neuralResults.entities.size}`,
current: entitiesProcessed,
total: neuralResults.entities.size,
entities: entitiesProcessed
})
}
}
// Store relationships using batch processing
if (neuralResults.relationships.size > 0) {
onProgress?.({
phase: 'storing-relationships',
message: 'Preparing relationships...',
current: 0,
total: neuralResults.relationships.size
})
// Collect all relationship parameters. Subtype `extracted` matches the
// entity-side label so consumers can query "everything from this neural pass"
// via `(type, subtype: 'extracted')` (added 7.30.1).
const relationshipParams: Array<{from: string; to: string; type: VerbType; subtype?: string; weight?: number; metadata?: any}> = []
for (const relation of neuralResults.relationships.values()) {
// Map to actual entity IDs
const sourceEntity = Array.from(neuralResults.entities.values())
.find(e => e.id === relation.from)
const targetEntity = Array.from(neuralResults.entities.values())
.find(e => e.id === relation.to)
if (sourceEntity && targetEntity) {
relationshipParams.push({
from: sourceEntity.id,
to: targetEntity.id,
type: relation.type,
subtype: (relation as any).subtype ?? 'extracted',
weight: relation.weight,
metadata: relation.metadata
})
totalConfidence += relation.confidence
}
}
// Batch create relationships with progress
if (relationshipParams.length > 0) {
const relationshipIds = await this.brain.relateMany({
items: relationshipParams,
parallel: true,
chunkSize: 100,
continueOnError: true,
onProgress: (done, total) => {
onProgress?.({
phase: 'storing-relationships',
message: `Building relationships: ${done}/${total}`,
current: done,
total: total,
entities: result.stats.entitiesCreated,
relationships: done
})
}
})
// Map results back
relationshipIds.forEach((id, index) => {
if (id && relationshipParams[index]) {
result.relationships.push({
id,
from: relationshipParams[index].from,
to: relationshipParams[index].to,
type: relationshipParams[index].type,
weight: relationshipParams[index].weight || 1,
confidence: 0.5, // Default confidence
metadata: relationshipParams[index].metadata
})
}
})
result.stats.relationshipsCreated = relationshipIds.length
}
}
// Calculate average confidence
const totalItems = result.stats.entitiesCreated + result.stats.relationshipsCreated
result.stats.averageConfidence = totalItems > 0 ? totalConfidence / totalItems : 0
return result
}
// Helper methods for parsing different formats
private detectFormat(data: string): string {
const trimmed = data.trim()
// JSON
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
(trimmed.startsWith('[') && trimmed.endsWith(']'))) {
return 'json'
}
// CSV (has commas and newlines)
if (trimmed.includes(',') && trimmed.includes('\n')) {
return 'csv'
}
// YAML (has colons and indentation)
if (trimmed.includes(':') && (trimmed.includes('\n ') || trimmed.includes('\n\t'))) {
return 'yaml'
}
// Markdown (has headers)
if (trimmed.includes('#') || trimmed.includes('```')) {
return 'markdown'
}
// HTML/XML
if (trimmed.includes('<') && trimmed.includes('>')) {
return trimmed.toLowerCase().includes('<!doctype html') ? 'html' : 'xml'
}
return 'text'
}
private parseCSV(data: string): any[] {
// Reuse the CSV parser from neural import
const lines = data.split('\n').filter(l => l.trim())
if (lines.length === 0) return []
const headers = lines[0].split(',').map(h => h.trim())
const results = []
for (let i = 1; i < lines.length; i++) {
const values = lines[i].split(',').map(v => v.trim())
const obj: any = {}
headers.forEach((header, index) => {
obj[header] = values[index] || ''
})
results.push(obj)
}
return results
}
private parseYAML(data: string): any[] {
// Simple YAML parser
const results = []
const lines = data.split('\n')
let current: any = null
for (const line of lines) {
const trimmed = line.trim()
if (!trimmed || trimmed.startsWith('#')) continue
if (trimmed.startsWith('- ')) {
// Array item
const value = trimmed.substring(2)
if (!current) {
results.push(value)
} else {
if (!current._items) current._items = []
current._items.push(value)
}
} else if (trimmed.includes(':')) {
// Key-value
const [key, ...valueParts] = trimmed.split(':')
const value = valueParts.join(':').trim()
if (!current) {
current = {}
results.push(current)
}
current[key.trim()] = value
}
}
return results.length > 0 ? results : [{ text: data }]
}
private parseMarkdown(data: string): any[] {
const results = []
const lines = data.split('\n')
let current: any = null
let inCodeBlock = false
for (const line of lines) {
if (line.startsWith('```')) {
inCodeBlock = !inCodeBlock
if (inCodeBlock && current) {
current.code = ''
}
continue
}
if (inCodeBlock && current) {
current.code += line + '\n'
} else if (line.startsWith('#')) {
// Header
const level = line.match(/^#+/)?.[0].length || 1
const text = line.replace(/^#+\s*/, '')
current = {
type: 'heading',
level,
text
}
results.push(current)
} else if (line.trim()) {
// Paragraph
if (!current || current.type !== 'paragraph') {
current = {
type: 'paragraph',
text: ''
}
results.push(current)
}
current.text += line + ' '
}
}
return results
}
private parseHTML(data: string): any[] {
// Simple HTML text extraction
const text = data
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove scripts
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '') // Remove styles
.replace(/<[^>]+>/g, ' ') // Remove tags
.replace(/\s+/g, ' ') // Normalize whitespace
.trim()
return this.extractFromText(text)
}
private generateId(data: any): string {
// Generate deterministic ID based on content
const text = this.dataToText(data)
const hash = this.simpleHash(text)
return `import_${hash}_${Date.now()}`
}
private simpleHash(text: string): string {
let hash = 0
for (let i = 0; i < text.length; i++) {
const char = text.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash
}
return Math.abs(hash).toString(36)
}
private async hashBinary(data: Uint8Array): Promise<string> {
// Simple binary hash
let hash = 0
for (let i = 0; i < Math.min(data.length, 1000); i++) {
hash = ((hash << 5) - hash) + data[i]
hash = hash & hash
}
return Math.abs(hash).toString(36)
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,10 +7,11 @@
*/ */
import chalk from 'chalk' import chalk from 'chalk'
import ora from 'ora' import ora, { type Ora } from 'ora'
import inquirer from 'inquirer' import inquirer from 'inquirer'
import { Brainy } from '../../brainy.js' import { Brainy } from '../../brainy.js'
import { BrainyTypes, NounType, VerbType } from '../../index.js' import { BrainyTypes, NounType, VerbType } from '../../index.js'
import type { Entity, Result } from '../../types/brainy.types.js'
interface CoreOptions { interface CoreOptions {
verbose?: boolean verbose?: boolean
@ -87,7 +88,7 @@ export const coreCommands = {
* Add data to the neural database * Add data to the neural database
*/ */
async add(text: string | undefined, options: AddOptions) { async add(text: string | undefined, options: AddOptions) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no text provided // Interactive mode if no text provided
if (!text) { if (!text) {
@ -222,7 +223,7 @@ export const coreCommands = {
* Search the neural database with Triple Intelligence * Search the neural database with Triple Intelligence
*/ */
async search(query: string | undefined, options: SearchOptions) { async search(query: string | undefined, options: SearchOptions) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no query provided // Interactive mode if no query provided
if (!query) { if (!query) {
@ -407,8 +408,13 @@ export const coreCommands = {
if (result.score !== undefined) { if (result.score !== undefined) {
console.log(chalk.green(` Score: ${(result.score * 100).toFixed(1)}%`)) console.log(chalk.green(` Score: ${(result.score * 100).toFixed(1)}%`))
if (options.explain && (result as any).scores) { // Per-intelligence sub-scores aren't part of the public Result
const scores = (result as any).scores // contract (the typed breakdown lives on `explanation`) — read
// defensively so --explain degrades to no breakdown when absent.
const scores = (result as Result & {
scores?: { vector?: number; graph?: number; field?: number }
}).scores
if (options.explain && scores) {
if (scores.vector !== undefined) { if (scores.vector !== undefined) {
console.log(chalk.dim(` Vector: ${(scores.vector * 100).toFixed(1)}%`)) console.log(chalk.dim(` Vector: ${(scores.vector * 100).toFixed(1)}%`))
} }
@ -422,24 +428,28 @@ export const coreCommands = {
} }
// Show type // Show type
if ((entity as any).type) { if (entity.type) {
console.log(chalk.dim(` Type: ${(entity as any).type}`)) console.log(chalk.dim(` Type: ${entity.type}`))
} }
// Show content preview // Show content preview. `content` is not a standard Entity field —
if ((entity as any).content) { // surfaced defensively for records that carry flattened text content.
const preview = (entity as any).content.substring(0, 80) const content = (entity as Entity & { content?: string }).content
console.log(chalk.dim(` Content: ${preview}${(entity as any).content.length > 80 ? '...' : ''}`)) if (content) {
const preview = content.substring(0, 80)
console.log(chalk.dim(` Content: ${preview}${content.length > 80 ? '...' : ''}`))
} }
// Show metadata // Show metadata
if ((entity as any).metadata && Object.keys((entity as any).metadata).length > 0) { if (entity.metadata && Object.keys(entity.metadata).length > 0) {
console.log(chalk.dim(` Metadata: ${JSON.stringify((entity as any).metadata)}`)) console.log(chalk.dim(` Metadata: ${JSON.stringify(entity.metadata)}`))
} }
// Show relationships // Show relationships. `relations` isn't part of the public Result
if (options.includeRelations && (result as any).relations) { // contract — read defensively so --include-relations degrades to
const relations = (result as any).relations // no output when find() doesn't attach it.
const relations = (result as Result & { relations?: unknown[] }).relations
if (options.includeRelations && relations) {
if (relations.length > 0) { if (relations.length > 0) {
console.log(chalk.dim(` Relations: ${relations.length} connections`)) console.log(chalk.dim(` Relations: ${relations.length} connections`))
} }
@ -487,7 +497,7 @@ export const coreCommands = {
* Get item by ID * Get item by ID
*/ */
async get(id: string | undefined, options: GetOptions) { async get(id: string | undefined, options: GetOptions) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no ID provided // Interactive mode if no ID provided
if (!id) { if (!id) {
@ -527,18 +537,25 @@ export const coreCommands = {
if (!options.json) { if (!options.json) {
console.log(chalk.cyan('\nItem Details:')) console.log(chalk.cyan('\nItem Details:'))
console.log(` ID: ${item.id}`) console.log(` ID: ${item.id}`)
console.log(` Content: ${(item as any).content || 'N/A'}`) // `content` is not a standard Entity field — see the search() note.
console.log(` Content: ${(item as Entity & { content?: string }).content || 'N/A'}`)
if (item.metadata) { if (item.metadata) {
console.log(` Metadata: ${JSON.stringify(item.metadata, null, 2)}`) console.log(` Metadata: ${JSON.stringify(item.metadata, null, 2)}`)
} }
if (options.withConnections) { if (options.withConnections) {
// Get verbs/relationships // Brainy's public API has no getConnections() — this optional probe
// Get connections if method exists // predates related() and resolves to an empty list on current
const connections = (brain as any).getConnections ? await (brain as any).getConnections(id) : [] // builds. Typed to match the legacy edge shape it rendered.
const legacyBrain = brain as Brainy & {
getConnections?: (id: string | undefined) => Promise<
Array<{ source: string; type: string; target: string }>
>
}
const connections = legacyBrain.getConnections ? await legacyBrain.getConnections(id) : []
if (connections && connections.length > 0) { if (connections && connections.length > 0) {
console.log(chalk.cyan('\nConnections:')) console.log(chalk.cyan('\nConnections:'))
connections.forEach((conn: any) => { connections.forEach((conn) => {
console.log(` ${conn.source} --[${conn.type}]--> ${conn.target}`) console.log(` ${conn.source} --[${conn.type}]--> ${conn.target}`)
}) })
} }
@ -561,7 +578,7 @@ export const coreCommands = {
* Create relationship between items * Create relationship between items
*/ */
async relate(source: string | undefined, verb: string | undefined, target: string | undefined, options: RelateOptions) { async relate(source: string | undefined, verb: string | undefined, target: string | undefined, options: RelateOptions) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if parameters missing // Interactive mode if parameters missing
if (!source || !verb || !target) { if (!source || !verb || !target) {
@ -630,10 +647,12 @@ export const coreCommands = {
// `--subtype <value>` → Brainy default `'cli-relate'`. Same rationale // `--subtype <value>` → Brainy default `'cli-relate'`. Same rationale
// as `brainy add` — guarantees CLI works under strict-mode brains // as `brainy add` — guarantees CLI works under strict-mode brains
// (added 7.30.1). // (added 7.30.1).
// The verb arrives as a raw CLI string; relate() validates it against
// the VerbType vocabulary at runtime.
const result = await brain.relate({ const result = await brain.relate({
from: source, from: source,
to: target, to: target,
type: verb as any, type: verb as VerbType,
subtype: options.subtype ?? 'cli-relate', subtype: options.subtype ?? 'cli-relate',
metadata metadata
}) })
@ -664,7 +683,7 @@ export const coreCommands = {
* Update an existing entity * Update an existing entity
*/ */
async update(id: string | undefined, options: AddOptions & { content?: string }) { async update(id: string | undefined, options: AddOptions & { content?: string }) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no ID provided // Interactive mode if no ID provided
if (!id) { if (!id) {
@ -770,10 +789,10 @@ export const coreCommands = {
}, },
/** /**
* Delete an entity * Remove an entity
*/ */
async deleteEntity(id: string | undefined, options: CoreOptions & { force?: boolean }) { async removeEntity(id: string | undefined, options: CoreOptions & { force?: boolean }) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no ID provided // Interactive mode if no ID provided
if (!id) { if (!id) {
@ -781,7 +800,7 @@ export const coreCommands = {
{ {
type: 'input', type: 'input',
name: 'id', name: 'id',
message: 'Entity ID to delete:', message: 'Entity ID to remove:',
validate: (input: string) => input.trim().length > 0 || 'ID cannot be empty' validate: (input: string) => input.trim().length > 0 || 'ID cannot be empty'
}, },
{ {
@ -793,7 +812,7 @@ export const coreCommands = {
]) ])
if (!answers.confirm) { if (!answers.confirm) {
console.log(chalk.yellow('Delete cancelled')) console.log(chalk.yellow('Operation cancelled'))
return return
} }
@ -803,36 +822,36 @@ export const coreCommands = {
const answer = await inquirer.prompt([{ const answer = await inquirer.prompt([{
type: 'confirm', type: 'confirm',
name: 'confirm', name: 'confirm',
message: `Delete entity ${id}? This cannot be undone.`, message: `Remove entity ${id}? This cannot be undone.`,
default: false default: false
}]) }])
if (!answer.confirm) { if (!answer.confirm) {
console.log(chalk.yellow('Delete cancelled')) console.log(chalk.yellow('Operation cancelled'))
return return
} }
} }
spinner = ora('Deleting entity...').start() spinner = ora('Removing entity...').start()
const brain = getBrainy() const brain = getBrainy()
await brain.init() await brain.init()
await brain.delete(id) await brain.remove(id)
spinner.succeed('Entity deleted successfully') spinner.succeed('Entity removed successfully')
if (!options.json) { if (!options.json) {
console.log(chalk.green(`Deleted entity: ${id}`)) console.log(chalk.green(`Removed entity: ${id}`))
} else { } else {
formatOutput({ id, deleted: true }, options) formatOutput({ id, removed: true }, options)
} }
// One-shot command — see add() for why the explicit close + exit. // One-shot command — see add() for why the explicit close + exit.
await brain.close() await brain.close()
process.exit(0) process.exit(0)
} catch (error: any) { } catch (error: any) {
if (spinner) spinner.fail('Failed to delete entity') if (spinner) spinner.fail('Failed to remove entity')
console.error(chalk.red('Delete failed:', error.message)) console.error(chalk.red('Remove failed:', error.message))
process.exit(1) process.exit(1)
} }
}, },
@ -841,7 +860,7 @@ export const coreCommands = {
* Remove a relationship * Remove a relationship
*/ */
async unrelate(id: string | undefined, options: CoreOptions & { force?: boolean }) { async unrelate(id: string | undefined, options: CoreOptions & { force?: boolean }) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no ID provided // Interactive mode if no ID provided
if (!id) { if (!id) {

View file

@ -10,7 +10,7 @@
* The legacy backup/import/export facade that used to live behind this * The legacy backup/import/export facade that used to live behind this
* command was superseded in 8.0: snapshots are `brainy snapshot` / `brainy * command was superseded in 8.0: snapshots are `brainy snapshot` / `brainy
* restore` (the Db API's `persist()`/`restore()`), and data ingestion is * restore` (the Db API's `persist()`/`restore()`), and data ingestion is
* `brainy import` (UniversalImportAPI). * `brainy import` (`brain.import()`).
*/ */
import chalk from 'chalk' import chalk from 'chalk'

View file

@ -3,7 +3,7 @@
* @description Import commands neural import and data import. * @description Import commands neural import and data import.
* *
* Complete import system exposing ALL Brainy import capabilities: * Complete import system exposing ALL Brainy import capabilities:
* - UniversalImportAPI: Neural import with AI type matching * - `brain.import()`: universal ingestion with neural type matching
* - DirectoryImporter: VFS directory imports * - DirectoryImporter: VFS directory imports
* *
* Supports: files, directories, URLs, all formats. Backup/restore is the * Supports: files, directories, URLs, all formats. Backup/restore is the
@ -11,19 +11,23 @@
*/ */
import chalk from 'chalk' import chalk from 'chalk'
import ora from 'ora' import ora, { type Ora } from 'ora'
import inquirer from 'inquirer' import inquirer from 'inquirer'
import { readFileSync, statSync, existsSync } from 'node:fs' import { readFileSync, statSync, existsSync } from 'node:fs'
import { Brainy } from '../../brainy.js' import { Brainy } from '../../brainy.js'
import { NounType } from '../../types/graphTypes.js' import { NounType } from '../../types/graphTypes.js'
import type { SupportedFormat } from '../../import/FormatDetector.js'
interface ImportOptions { interface ImportOptions {
verbose?: boolean verbose?: boolean
json?: boolean json?: boolean
pretty?: boolean pretty?: boolean
quiet?: boolean quiet?: boolean
// Format options // Format options. Populated by commander straight from `--format` with no
format?: 'json' | 'csv' | 'jsonl' | 'yaml' | 'markdown' | 'html' | 'xml' | 'text' // validation, so the runtime value is whatever the user typed — kept as a
// plain string and narrowed to brain.import()'s SupportedFormat at the call
// boundary (the import extractor rejects unsupported values at runtime).
format?: string
// Import behavior // Import behavior
recursive?: boolean recursive?: boolean
batchSize?: string batchSize?: string
@ -60,11 +64,11 @@ const formatOutput = (data: any, options: ImportOptions): void => {
export const importCommands = { export const importCommands = {
/** /**
* Enhanced import using UniversalImportAPI * Enhanced import via `brain.import()`.
* Supports files, directories, URLs, all formats * Supports files, directories, URLs, all formats.
*/ */
async import(source: string | undefined, options: ImportOptions) { async import(source: string | undefined, options: ImportOptions) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no source provided // Interactive mode if no source provided
if (!source) { if (!source) {
@ -260,7 +264,8 @@ export const importCommands = {
} else { } else {
// File import with progress // File import with progress
result = await brain.import(source, { result = await brain.import(source, {
format: options.format as any, // Raw CLI string → SupportedFormat (see ImportOptions.format note).
format: options.format as SupportedFormat | undefined,
enableNeuralExtraction: options.extractEntities !== false, enableNeuralExtraction: options.extractEntities !== false,
enableRelationshipInference: options.detectRelationships !== false, enableRelationshipInference: options.detectRelationships !== false,
enableConceptExtraction: options.extractConcepts || false, enableConceptExtraction: options.extractConcepts || false,
@ -422,7 +427,7 @@ export const importCommands = {
* VFS-specific import (files/directories into VFS) * VFS-specific import (files/directories into VFS)
*/ */
async vfsImport(source: string | undefined, options: ImportOptions) { async vfsImport(source: string | undefined, options: ImportOptions) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no source provided // Interactive mode if no source provided
if (!source) { if (!source) {

View file

@ -5,7 +5,7 @@
*/ */
import chalk from 'chalk' import chalk from 'chalk'
import ora from 'ora' import ora, { type Ora } from 'ora'
import inquirer from 'inquirer' import inquirer from 'inquirer'
import Table from 'cli-table3' import Table from 'cli-table3'
import { Brainy } from '../../brainy.js' import { Brainy } from '../../brainy.js'
@ -192,7 +192,7 @@ export const insightsCommands = {
* Get field values for a specific field * Get field values for a specific field
*/ */
async fieldValues(field: string | undefined, options: InsightsOptions & { limit?: string }) { async fieldValues(field: string | undefined, options: InsightsOptions & { limit?: string }) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no field provided // Interactive mode if no field provided
if (!field) { if (!field) {
@ -274,7 +274,7 @@ export const insightsCommands = {
* Get optimal query plan for filters * Get optimal query plan for filters
*/ */
async queryPlan(options: InsightsOptions & { filters?: string }) { async queryPlan(options: InsightsOptions & { filters?: string }) {
let spinner: any = null let spinner: Ora | null = null
try { try {
let filters: Record<string, any> = {} let filters: Record<string, any> = {}

View file

@ -15,6 +15,8 @@
import chalk from 'chalk' import chalk from 'chalk'
import ora from 'ora' import ora from 'ora'
import { Brainy } from '../../brainy.js' import { Brainy } from '../../brainy.js'
import type { RelatedParams } from '../../types/brainy.types.js'
import type { NounType, VerbType } from '../../types/graphTypes.js'
interface InspectOptions { interface InspectOptions {
fresh?: boolean fresh?: boolean
@ -109,8 +111,10 @@ export const inspectCommands = {
const where = options.where ? JSON.parse(options.where) : undefined const where = options.where ? JSON.parse(options.where) : undefined
const limit = options.limit ? parseInt(options.limit, 10) : 20 const limit = options.limit ? parseInt(options.limit, 10) : 20
const offset = options.offset ? parseInt(options.offset, 10) : 0 const offset = options.offset ? parseInt(options.offset, 10) : 0
// --type arrives as a raw CLI string; find() resolves it against the
// NounType vocabulary at runtime.
const results = await brain.find({ const results = await brain.find({
type: options.type as any, type: options.type as NounType | undefined,
where, where,
limit, limit,
offset offset
@ -158,12 +162,16 @@ export const inspectCommands = {
if (spinner) spinner.text = 'Walking graph…' if (spinner) spinner.text = 'Walking graph…'
const direction = options.direction ?? 'both' const direction = options.direction ?? 'both'
const limit = options.limit ? parseInt(options.limit, 10) : 50 const limit = options.limit ? parseInt(options.limit, 10) : 50
const rels = await brain.getRelations({ // `direction` is accepted by the CLI surface but RelatedParams has
// no such filter — related() reads from/to/type/limit and ignores
// extras, so the assertion only admits the extra key, it doesn't change
// what the query does. --type arrives as a raw CLI string.
const rels = await brain.related({
from: id, from: id,
direction, direction,
type: options.type as any, type: options.type as VerbType | undefined,
limit limit
} as any) } as RelatedParams)
spinner?.succeed(`Found ${rels.length} relationship(s)`) spinner?.succeed(`Found ${rels.length} relationship(s)`)
emit(rels, options) emit(rels, options)
await brain.close() await brain.close()
@ -185,7 +193,7 @@ export const inspectCommands = {
const brain = await openReader(path, options) const brain = await openReader(path, options)
if (spinner) spinner.text = 'Planning query…' if (spinner) spinner.text = 'Planning query…'
const where = options.where ? JSON.parse(options.where) : {} const where = options.where ? JSON.parse(options.where) : {}
const plan = await brain.explain({ type: options.type as any, where }) const plan = await brain.explain({ type: options.type as NounType | undefined, where })
spinner?.succeed('Plan ready') spinner?.succeed('Plan ready')
emit(plan, options) emit(plan, options)
await brain.close() await brain.close()
@ -228,7 +236,7 @@ export const inspectCommands = {
// dedicated random-sample API in Brainy core for this v1. // dedicated random-sample API in Brainy core for this v1.
const window = Math.min(Math.max(n * 10, 50), 1000) const window = Math.min(Math.max(n * 10, 50), 1000)
const pool = await brain.find({ const pool = await brain.find({
type: options.type as any, type: options.type as NounType | undefined,
limit: window limit: window
}) })
const sample = pool const sample = pool
@ -275,7 +283,7 @@ export const inspectCommands = {
let offset = 0 let offset = 0
while (true) { while (true) {
const page = await brain.find({ const page = await brain.find({
type: options.type as any, type: options.type as NounType | undefined,
limit: batch, limit: batch,
offset offset
}) })
@ -310,7 +318,7 @@ export const inspectCommands = {
try { try {
const brain = await openReader(path, { ...options, quiet: true }) const brain = await openReader(path, { ...options, quiet: true })
const page = await brain.find({ const page = await brain.find({
type: options.type as any, type: options.type as NounType | undefined,
limit: 200 limit: 200
}) })
for (const e of page) { for (const e of page) {

View file

@ -10,10 +10,11 @@
import inquirer from 'inquirer' import inquirer from 'inquirer'
import chalk from 'chalk' import chalk from 'chalk'
import ora from 'ora' import ora, { type Ora } from 'ora'
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import { Brainy } from '../../brainy.js' import { Brainy } from '../../brainy.js'
import type { NeuralClusterParams } from '../../types/brainy.types.js'
interface CommandArguments { interface CommandArguments {
action?: string; action?: string;
@ -113,10 +114,12 @@ async function handleSimilarCommand(neural: any, argv: CommandArguments): Promis
} }
async function handleClustersCommand(neural: any, argv: CommandArguments): Promise<void> { async function handleClustersCommand(neural: any, argv: CommandArguments): Promise<void> {
let spinner: any = null let spinner: Ora | null = null
try { try {
let options: any = { let options: any = {
algorithm: argv.algorithm as any, // --algorithm arrives as a raw CLI string; the interactive prompt below
// and neural.clusters() constrain it to the supported algorithms.
algorithm: argv.algorithm as NeuralClusterParams['algorithm'],
threshold: argv.threshold, threshold: argv.threshold,
maxClusters: argv.limit maxClusters: argv.limit
} }

View file

@ -5,7 +5,7 @@
*/ */
import chalk from 'chalk' import chalk from 'chalk'
import ora from 'ora' import ora, { type Ora } from 'ora'
import inquirer from 'inquirer' import inquirer from 'inquirer'
import Table from 'cli-table3' import Table from 'cli-table3'
import { Brainy } from '../../brainy.js' import { Brainy } from '../../brainy.js'
@ -37,7 +37,7 @@ export const nlpCommands = {
* Extract entities from text * Extract entities from text
*/ */
async extract(text: string | undefined, options: NLPOptions) { async extract(text: string | undefined, options: NLPOptions) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no text provided // Interactive mode if no text provided
if (!text) { if (!text) {
@ -126,7 +126,7 @@ export const nlpCommands = {
* Extract concepts from text * Extract concepts from text
*/ */
async extractConcepts(text: string | undefined, options: NLPOptions & { threshold?: string }) { async extractConcepts(text: string | undefined, options: NLPOptions & { threshold?: string }) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no text provided // Interactive mode if no text provided
if (!text) { if (!text) {
@ -197,7 +197,7 @@ export const nlpCommands = {
* Analyze text with full NLP pipeline * Analyze text with full NLP pipeline
*/ */
async analyze(text: string | undefined, options: NLPOptions) { async analyze(text: string | undefined, options: NLPOptions) {
let spinner: any = null let spinner: Ora | null = null
try { try {
// Interactive mode if no text provided // Interactive mode if no text provided
if (!text) { if (!text) {

View file

@ -6,7 +6,7 @@
* - `storage status` backend, root directory, entity/relationship counts, * - `storage status` backend, root directory, entity/relationship counts,
* writer-lock holder, and operational mode (rendered from `brain.stats()`). * writer-lock holder, and operational mode (rendered from `brain.stats()`).
* - `storage batch-delete <file>` delete a newline-separated list of entity * - `storage batch-delete <file>` delete a newline-separated list of entity
* IDs through the public `brain.delete()` path (vectors, metadata, indexes, * IDs through the public `brain.remove()` path (vectors, metadata, indexes,
* and statistics all stay consistent), with per-ID retry and an optional * and statistics all stay consistent), with per-ID retry and an optional
* continue-on-error mode. * continue-on-error mode.
* *
@ -144,7 +144,7 @@ export const storageCommands = {
/** /**
* @description Delete a list of entity IDs (one per line in a file) through * @description Delete a list of entity IDs (one per line in a file) through
* the public `brain.delete()` path so vectors, metadata, graph edges, * the public `brain.remove()` path so vectors, metadata, graph edges,
* indexes, and statistics all stay consistent. Asks for confirmation before * indexes, and statistics all stay consistent. Asks for confirmation before
* deleting; failed IDs are retried up to `--max-retries` times, and * deleting; failed IDs are retried up to `--max-retries` times, and
* `--continue-on-error` keeps going past IDs that still fail. * `--continue-on-error` keeps going past IDs that still fail.
@ -204,7 +204,7 @@ export const storageCommands = {
for (let attempt = 0; attempt <= maxRetries; attempt++) { for (let attempt = 0; attempt <= maxRetries; attempt++) {
try { try {
await brain.delete(id) await brain.remove(id)
succeeded = true succeeded = true
break break
} catch (error) { } catch (error) {

View file

@ -30,6 +30,15 @@ interface BenchmarkOptions extends UtilityOptions {
iterations?: string iterations?: string
} }
/** Per-operation timing summary produced by `brainy benchmark`. */
interface BenchmarkOperationStats {
avg: string
min: number
max: number
median: number
ops: string
}
let brainyInstance: Brainy | null = null let brainyInstance: Brainy | null = null
const getBrainy = (): Brainy => { const getBrainy = (): Brainy => {
@ -188,7 +197,10 @@ export const utilityCommands = {
console.log(chalk.cyan(`\n🚀 Running Benchmarks (${iterations} iterations)\n`)) console.log(chalk.cyan(`\n🚀 Running Benchmarks (${iterations} iterations)\n`))
const results: any = { const results: {
operations: Record<string, BenchmarkOperationStats>
summary: { totalOperations?: number; averageOpsPerSec?: string }
} = {
operations: {}, operations: {},
summary: {} summary: {}
} }
@ -253,7 +265,7 @@ export const utilityCommands = {
} }
// Calculate summary // Calculate summary
const totalOps: number = (Object.values(results.operations) as any[]).reduce((sum: number, op: any) => const totalOps: number = Object.values(results.operations).reduce((sum: number, op) =>
sum + parseFloat(op.ops), 0) sum + parseFloat(op.ops), 0)
results.summary = { results.summary = {
@ -277,7 +289,7 @@ export const utilityCommands = {
style: { head: [], border: [] } style: { head: [], border: [] }
}) })
Object.entries(results.operations).forEach(([op, stats]: [string, any]) => { Object.entries(results.operations).forEach(([op, stats]) => {
table.push([ table.push([
op, op,
stats.avg, stats.avg,

View file

@ -9,6 +9,7 @@ import ora from 'ora'
import Table from 'cli-table3' import Table from 'cli-table3'
import { readFileSync, writeFileSync } from 'node:fs' import { readFileSync, writeFileSync } from 'node:fs'
import { Brainy } from '../../brainy.js' import { Brainy } from '../../brainy.js'
import type { SearchResult, VFSDirent } from '../../vfs/types.js'
interface VFSOptions { interface VFSOptions {
verbose?: boolean verbose?: boolean
@ -54,8 +55,9 @@ export const vfsCommands = {
try { try {
const brain = await getBrainy() // Await async getBrainy const brain = await getBrainy() // Await async getBrainy
// VFS auto-initialized, no need for vfs.init() // VFS auto-initialized, no need for vfs.init()
// --encoding arrives as a raw CLI string (Node validates it at use time).
const buffer = await brain.vfs.readFile(path, { const buffer = await brain.vfs.readFile(path, {
encoding: options.encoding as any encoding: options.encoding as BufferEncoding | undefined
}) })
spinner.succeed('File read successfully') spinner.succeed('File read successfully')
@ -103,8 +105,9 @@ export const vfsCommands = {
process.exit(1) process.exit(1)
} }
// --encoding arrives as a raw CLI string (Node validates it at use time).
await brain.vfs.writeFile(path, data, { await brain.vfs.writeFile(path, data, {
encoding: options.encoding as any encoding: options.encoding as BufferEncoding | undefined
}) })
spinner.succeed('File written successfully') spinner.succeed('File written successfully')
@ -135,7 +138,9 @@ export const vfsCommands = {
try { try {
const brain = await getBrainy() const brain = await getBrainy()
const entries = await brain.vfs.readdir(path, { withFileTypes: true }) // withFileTypes: true selects the VFSDirent[] branch of readdir's
// `string[] | VFSDirent[]` union return type.
const entries = await brain.vfs.readdir(path, { withFileTypes: true }) as VFSDirent[]
spinner.succeed(`Found ${Array.isArray(entries) ? entries.length : 0} items`) spinner.succeed(`Found ${Array.isArray(entries) ? entries.length : 0} items`)
@ -154,13 +159,14 @@ export const vfsCommands = {
style: { head: [], border: [] } style: { head: [], border: [] }
}) })
for (const entry of entries as any[]) { for (const entry of entries) {
if (!options.all && entry.name.startsWith('.')) continue if (!options.all && entry.name.startsWith('.')) continue
const isDirectory = entry.type === 'directory'
const stat = await brain.vfs.stat(`${path}/${entry.name}`) const stat = await brain.vfs.stat(`${path}/${entry.name}`)
table.push([ table.push([
entry.isDirectory() ? chalk.blue('DIR') : 'FILE', isDirectory ? chalk.blue('DIR') : 'FILE',
entry.isDirectory() ? '-' : formatBytes(stat.size), isDirectory ? '-' : formatBytes(stat.size),
formatDate(stat.mtime), formatDate(stat.mtime),
entry.name entry.name
]) ])
@ -170,10 +176,10 @@ export const vfsCommands = {
} else { } else {
// Simple format // Simple format
console.log() console.log()
for (const entry of entries as any[]) { for (const entry of entries) {
if (!options.all && entry.name.startsWith('.')) continue if (!options.all && entry.name.startsWith('.')) continue
if (entry.isDirectory()) { if (entry.type === 'directory') {
console.log(chalk.blue(entry.name + '/')) console.log(chalk.blue(entry.name + '/'))
} else { } else {
console.log(entry.name) console.log(entry.name)
@ -322,8 +328,11 @@ export const vfsCommands = {
if (result.score) { if (result.score) {
console.log(chalk.dim(` Score: ${(result.score * 100).toFixed(1)}%`)) console.log(chalk.dim(` Score: ${(result.score * 100).toFixed(1)}%`))
} }
if ((result as any).excerpt) { // `excerpt` isn't part of the VFS SearchResult contract — search()
console.log(chalk.dim(` ${(result as any).excerpt}`)) // doesn't attach it today; read defensively.
const excerpt = (result as SearchResult & { excerpt?: string }).excerpt
if (excerpt) {
console.log(chalk.dim(` ${excerpt}`))
} }
console.log() console.log()
}) })

View file

@ -46,7 +46,7 @@ ${chalk.cyan('Examples:')}
$ brainy add "React is a JavaScript library" $ brainy add "React is a JavaScript library"
$ brainy find "JavaScript frameworks" $ brainy find "JavaScript frameworks"
$ brainy update <id> --content "Updated content" $ brainy update <id> --content "Updated content"
$ brainy delete <id> ${chalk.dim('# Requires confirmation')} $ brainy remove <id> ${chalk.dim('# Requires confirmation')}
$ brainy search "react" --type Component --where '{"tested":true}' $ brainy search "react" --type Component --where '{"tested":true}'
${chalk.dim('# Neural API')} ${chalk.dim('# Neural API')}
@ -143,10 +143,10 @@ program
.action(coreCommands.update) .action(coreCommands.update)
program program
.command('delete [id]') .command('remove [id]')
.description('Delete an entity (interactive if no ID, requires confirmation)') .description('Remove an entity (interactive if no ID, requires confirmation)')
.option('-f, --force', 'Skip confirmation prompt') .option('-f, --force', 'Skip confirmation prompt')
.action(coreCommands.deleteEntity) .action(coreCommands.removeEntity)
program program
.command('unrelate [id]') .command('unrelate [id]')
@ -472,7 +472,7 @@ program
.addCommand( .addCommand(
new Command('batch-delete') new Command('batch-delete')
.argument('<file>', 'File containing entity IDs (one per line)') .argument('<file>', 'File containing entity IDs (one per line)')
.description('Delete a list of entities through the public delete path (with retry)') .description('Delete a list of entities through the public remove() path (with retry)')
.option('--max-retries <n>', 'Maximum retry attempts per ID', '3') .option('--max-retries <n>', 'Maximum retry attempts per ID', '3')
.option('--continue-on-error', 'Continue past IDs that still fail after retries') .option('--continue-on-error', 'Continue past IDs that still fail after retries')
.action((file, options) => { .action((file, options) => {

View file

@ -126,7 +126,10 @@ class ConfigurationRegistry {
if (await provider.detect()) { if (await provider.detect()) {
const config = await provider.getConfig() const config = await provider.getConfig()
return { return {
type: provider.type as any, // Registered providers deliberately extend the built-in storage
// vocabulary ('redis', 'mongodb', ...) — the registry contract is
// open, so narrow the provider's free-form type string here.
type: provider.type as StorageConfigResult['type'],
config, config,
reason: `Auto-detected ${provider.name}`, reason: `Auto-detected ${provider.name}`,
autoSelected: true autoSelected: true
@ -313,7 +316,11 @@ export function registerPresetAugmentation(name: string, config: PresetConfig):
* Example preset for Redis-based caching service * Example preset for Redis-based caching service
*/ */
export const redisCachePreset: PresetConfig = { export const redisCachePreset: PresetConfig = {
storage: 'redis' as any, // Extended storage type // 'redis' is an extended storage type outside the built-in StorageOption
// enum — extension presets resolve it at runtime through the provider
// registry, so this is a genuine typed boundary (hence the double
// assertion: the literal has no overlap with the enum).
storage: 'redis' as unknown as StorageOption,
model: ModelPrecision.Q8, model: ModelPrecision.Q8,
features: ['core', 'cache', 'search'], features: ['core', 'cache', 'search'],
distributed: true, distributed: true,

View file

@ -149,7 +149,9 @@ export async function processZeroConfig(input?: string | BrainyZeroConfig): Prom
// Handle string shorthand (preset name) // Handle string shorthand (preset name)
if (typeof input === 'string') { if (typeof input === 'string') {
if (input in PRESETS) { if (input in PRESETS) {
config = { mode: input as any } // The `in` guard above proved the string is a preset key, which is
// exactly the BrainyZeroConfig mode union.
config = { mode: input as keyof typeof PRESETS }
} else { } else {
throw new Error(`Unknown preset: ${input}. Valid presets: ${Object.keys(PRESETS).join(', ')}`) throw new Error(`Unknown preset: ${input}. Valid presets: ${Object.keys(PRESETS).join(', ')}`)
} }
@ -263,7 +265,17 @@ export async function processZeroConfig(input?: string | BrainyZeroConfig): Prom
// Apply distributed preset settings if applicable // Apply distributed preset settings if applicable
if (config.mode === 'writer' || config.mode === 'reader') { if (config.mode === 'writer' || config.mode === 'reader') {
const presetSettings = PRESETS[config.mode] as any // Cast to any since we know these presets have additional properties // The writer/reader presets each carry their own subset of the
// distributed flags beyond the base preset shape — widen to the union of
// those flags so both branches read uniformly.
const presetSettings: {
distributed: boolean
role: 'writer' | 'reader'
readOnly?: boolean
writeOnly?: boolean
allowDirectReads?: boolean
lazyLoadInReadOnlyMode?: boolean
} = PRESETS[config.mode]
// Apply distributed-specific settings // Apply distributed-specific settings
finalConfig.distributed = presetSettings.distributed finalConfig.distributed = presetSettings.distributed

View file

@ -389,7 +389,7 @@ export interface HNSWVerbWithMetadata {
// `ReportsTo` relationship might have subtype 'direct' vs 'dotted-line'; a `RelatedTo` // `ReportsTo` relationship might have subtype 'direct' vs 'dotted-line'; a `RelatedTo`
// edge might carry 'spouse' / 'sibling' / 'colleague'). Flat string (no hierarchy) — // edge might carry 'spouse' / 'sibling' / 'colleague'). Flat string (no hierarchy) —
// consumers decide the vocabulary. Indexed and rolled up into per-VerbType statistics // consumers decide the vocabulary. Indexed and rolled up into per-VerbType statistics
// so it's queryable (`getRelations({ verb, subtype })`) and aggregable // so it's queryable (`related({ verb, subtype })`) and aggregable
// (`groupBy:['subtype']`) on the standard-field fast path, never falling through to // (`groupBy:['subtype']`) on the standard-field fast path, never falling through to
// the metadata fallback. // the metadata fallback.
subtype?: string subtype?: string
@ -417,7 +417,7 @@ export interface HNSWVerbWithMetadata {
* Verb representing a relationship between nouns * Verb representing a relationship between nouns
* Stored separately from the HNSW vector index for lightweight performance. * Stored separately from the HNSW vector index for lightweight performance.
* This is the canonical verb shape Brainy's internal graph engine, the public * This is the canonical verb shape Brainy's internal graph engine, the public
* `relate()` / `getRelations()` surface, and storage adapters all speak it. * `relate()` / `related()` surface, and storage adapters all speak it.
*/ */
export interface GraphVerb { export interface GraphVerb {
id: string // Unique identifier — always a UUID (8.0 contract: brainy generates every verb id, so providers may key verb-int interning on the raw UUID bytes) id: string // Unique identifier — always a UUID (8.0 contract: brainy generates every verb id, so providers may key verb-int interning on the raw UUID bytes)

View file

@ -1,140 +0,0 @@
/**
* MODEL GUARDIAN - CRITICAL PATH
*
* THIS IS THE MOST CRITICAL COMPONENT OF BRAINY
* Without the exact model, users CANNOT access their data
*
* Requirements:
* 1. Model MUST be all-MiniLM-L6-v2-q8 (bundled in package)
* 2. Model MUST be available at runtime (embedded in npm package)
* 3. Model MUST produce consistent 384-dim embeddings
* 4. System MUST fail fast if model unavailable in production
*/
import { WASMEmbeddingEngine } from '../embeddings/wasm/index.js'
// CRITICAL: These values MUST NEVER CHANGE
const CRITICAL_MODEL_CONFIG = {
modelName: 'all-MiniLM-L6-v2-q8',
embeddingDimensions: 384,
// Model is bundled in package - no external downloads needed
bundled: true
}
export class ModelGuardian {
private static instance: ModelGuardian
private isVerified = false
private lastVerification: Date | null = null
private constructor() {
// Model is bundled - no path detection needed
}
static getInstance(): ModelGuardian {
if (!ModelGuardian.instance) {
ModelGuardian.instance = new ModelGuardian()
}
return ModelGuardian.instance
}
/**
* CRITICAL: Verify model availability and integrity
* This MUST be called before any embedding operations
*/
async ensureCriticalModel(): Promise<void> {
// Check if already verified in this session
if (this.isVerified && this.lastVerification) {
const hoursSinceVerification =
(Date.now() - this.lastVerification.getTime()) / (1000 * 60 * 60)
if (hoursSinceVerification < 24) {
return
}
}
// Verify the bundled WASM model works
const modelWorks = await this.verifyBundledModel()
if (modelWorks) {
this.isVerified = true
this.lastVerification = new Date()
return
}
// CRITICAL FAILURE
throw new Error(
'🚨 CRITICAL FAILURE: Bundled transformer model not working!\n' +
'The model is REQUIRED for Brainy to function.\n' +
'Users CANNOT access their data without it.\n' +
'This indicates a package installation issue.'
)
}
/**
* Verify the bundled WASM model works correctly
*/
private async verifyBundledModel(): Promise<boolean> {
try {
const engine = WASMEmbeddingEngine.getInstance()
// Initialize the engine (loads bundled model)
await engine.initialize()
// Test embedding generation
const testEmbedding = await engine.embed('test verification')
// Verify dimensions
if (testEmbedding.length !== CRITICAL_MODEL_CONFIG.embeddingDimensions) {
console.error(
`❌ CRITICAL: Model dimension mismatch!\n` +
`Expected: ${CRITICAL_MODEL_CONFIG.embeddingDimensions}\n` +
`Got: ${testEmbedding.length}`
)
return false
}
// Verify normalization (should be unit length)
const norm = Math.sqrt(testEmbedding.reduce((sum, v) => sum + v * v, 0))
if (Math.abs(norm - 1.0) > 0.01) {
console.error(`❌ CRITICAL: Embeddings not normalized! Norm: ${norm}`)
return false
}
return true
} catch (error) {
console.error('❌ Model verification failed:', error)
return false
}
}
/**
* Get model status for diagnostics
*/
async getStatus(): Promise<{
verified: boolean
lastVerification: Date | null
modelName: string
dimensions: number
bundled: boolean
}> {
return {
verified: this.isVerified,
lastVerification: this.lastVerification,
modelName: CRITICAL_MODEL_CONFIG.modelName,
dimensions: CRITICAL_MODEL_CONFIG.embeddingDimensions,
bundled: CRITICAL_MODEL_CONFIG.bundled
}
}
/**
* Force re-verification (for testing)
*/
async forceReverify(): Promise<void> {
this.isVerified = false
this.lastVerification = null
await this.ensureCriticalModel()
}
}
// Export singleton instance
export const modelGuardian = ModelGuardian.getInstance()

View file

@ -1,193 +0,0 @@
/**
* Expanded Keyword Dictionary for Semantic Type Inference
*
* Comprehensive keyword-to-type mappings including:
* - Canonical keywords (primary terms)
* - Synonyms (alternative terms with slightly lower confidence)
* - Domain-specific variations
* - Common abbreviations
*
* Expanded from 767 1500+ keywords for better semantic coverage
*/
import { NounType } from '../types/graphTypes.js'
export interface KeywordDefinition {
keyword: string
type: NounType
confidence: number // 0.7-0.95 (higher = more canonical)
isCanonical: boolean // True for primary terms, false for synonyms
}
/**
* Expanded keyword dictionary (1500+ keywords for 31 NounTypes)
*/
export const EXPANDED_KEYWORD_DICTIONARY: KeywordDefinition[] = [
// ========== Person - Medical Professions ==========
// Canonical
{ keyword: 'doctor', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'physician', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'surgeon', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'nurse', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'cardiologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'oncologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'neurologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'psychiatrist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'psychologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'radiologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'pathologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'anesthesiologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'dermatologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'pediatrician', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'obstetrician', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'gynecologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'ophthalmologist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'dentist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'orthodontist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'pharmacist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'paramedic', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'therapist', type: NounType.Person, confidence: 0.90, isCanonical: true },
// Synonyms
{ keyword: 'medic', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'practitioner', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'clinician', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'medical professional', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'healthcare worker', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'medical doctor', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'registered nurse', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'emt', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'counselor', type: NounType.Person, confidence: 0.85, isCanonical: false },
// ========== Person - Engineering & Tech ==========
// Canonical
{ keyword: 'engineer', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'developer', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'programmer', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'architect', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'designer', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'technician', type: NounType.Person, confidence: 0.90, isCanonical: true },
// Synonyms
{ keyword: 'coder', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'software engineer', type: NounType.Person, confidence: 0.95, isCanonical: false },
{ keyword: 'software developer', type: NounType.Person, confidence: 0.95, isCanonical: false },
{ keyword: 'web developer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'frontend developer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'backend developer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'full stack developer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'devops engineer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'data engineer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'ml engineer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'machine learning engineer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'data scientist', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'ux designer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'ui designer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'graphic designer', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'systems architect', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'solutions architect', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'tech lead', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'techie', type: NounType.Person, confidence: 0.80, isCanonical: false },
// ========== Person - Management & Leadership ==========
// Canonical
{ keyword: 'manager', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'director', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'executive', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'leader', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'ceo', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'cto', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'cfo', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'coo', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'president', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'founder', type: NounType.Person, confidence: 0.95, isCanonical: true },
// Synonyms
{ keyword: 'supervisor', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'coordinator', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'vp', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'vice president', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'owner', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'product manager', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'project manager', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'engineering manager', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'team lead', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'chief executive officer', type: NounType.Person, confidence: 0.95, isCanonical: false },
{ keyword: 'chief technology officer', type: NounType.Person, confidence: 0.95, isCanonical: false },
{ keyword: 'chief financial officer', type: NounType.Person, confidence: 0.95, isCanonical: false },
// ========== Person - Professional Services ==========
// Canonical
{ keyword: 'analyst', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'consultant', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'specialist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'expert', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'professional', type: NounType.Person, confidence: 0.85, isCanonical: true },
{ keyword: 'lawyer', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'attorney', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'accountant', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'auditor', type: NounType.Person, confidence: 0.90, isCanonical: true },
// Synonyms
{ keyword: 'advisor', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'counselor', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'paralegal', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'legal counsel', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'business analyst', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'financial analyst', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'data analyst', type: NounType.Person, confidence: 0.90, isCanonical: false },
// ========== Person - Education & Research ==========
// Canonical
{ keyword: 'teacher', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'professor', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'researcher', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'scientist', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'student', type: NounType.Person, confidence: 0.95, isCanonical: true },
// Synonyms
{ keyword: 'instructor', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'educator', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'tutor', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'scholar', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'academic', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'pupil', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'learner', type: NounType.Person, confidence: 0.80, isCanonical: false },
{ keyword: 'trainee', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'intern', type: NounType.Person, confidence: 0.85, isCanonical: false },
// ========== Person - Creative Professions ==========
// Canonical
{ keyword: 'artist', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'musician', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'writer', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'author', type: NounType.Person, confidence: 0.90, isCanonical: true },
// Synonyms
{ keyword: 'painter', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'sculptor', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'performer', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'journalist', type: NounType.Person, confidence: 0.90, isCanonical: false },
{ keyword: 'editor', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'reporter', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'content creator', type: NounType.Person, confidence: 0.80, isCanonical: false },
{ keyword: 'blogger', type: NounType.Person, confidence: 0.80, isCanonical: false },
// ========== Person - General ==========
{ keyword: 'person', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'people', type: NounType.Person, confidence: 0.95, isCanonical: true },
{ keyword: 'individual', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'human', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'employee', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'worker', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'staff', type: NounType.Person, confidence: 0.90, isCanonical: true },
{ keyword: 'personnel', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'member', type: NounType.Person, confidence: 0.85, isCanonical: false },
{ keyword: 'team', type: NounType.Person, confidence: 0.80, isCanonical: false },
// Continuing with the rest... (this is getting long, so I'll create a comprehensive version)
// Let me structure this better by importing from the existing typeInference and expanding it
]
// Note: This file will be completed with all 1500+ keywords in the actual implementation
// For now, this shows the structure and approach

View file

@ -52,7 +52,7 @@ import type {
Entity, Entity,
FindParams, FindParams,
GetOptions, GetOptions,
GetRelationsParams, RelatedParams,
Relation, Relation,
Result Result
} from '../types/brainy.types.js' } from '../types/brainy.types.js'
@ -61,6 +61,7 @@ import {
splitVerbMetadataRecord splitVerbMetadataRecord
} from '../types/reservedFields.js' } from '../types/reservedFields.js'
import { v4 as uuidv4 } from '../universal/uuid.js' import { v4 as uuidv4 } from '../universal/uuid.js'
import { EntityNotFoundError } from '../errors/notFound.js'
import { SpeculativeOverlayError } from './errors.js' import { SpeculativeOverlayError } from './errors.js'
import type { GenerationStore } from './generationStore.js' import type { GenerationStore } from './generationStore.js'
import type { ChangedIds, TransactReceipt, TxOperation } from './types.js' import type { ChangedIds, TransactReceipt, TxOperation } from './types.js'
@ -76,8 +77,8 @@ import { entityMatchesFind, resolveEntityField, UnsupportedWhereOperatorError }
export interface HistoricalQueryHandle<T = any> { export interface HistoricalQueryHandle<T = any> {
/** Full `find()` surface (vector/semantic/graph/cursor/aggregate) at the pinned generation. */ /** Full `find()` surface (vector/semantic/graph/cursor/aggregate) at the pinned generation. */
find(query: string | FindParams<T>): Promise<Result<T>[]> find(query: string | FindParams<T>): Promise<Result<T>[]>
/** Full `getRelations()` surface (including cursor pagination) at the pinned generation. */ /** Full `related()` surface (including cursor pagination) at the pinned generation. */
getRelations(paramsOrId?: string | GetRelationsParams): Promise<Relation<T>[]> related(paramsOrId?: string | RelatedParams): Promise<Relation<T>[]>
/** Free the materialized indexes (closes the ephemeral reader). Idempotent. */ /** Free the materialized indexes (closes the ephemeral reader). Idempotent. */
close(): Promise<void> close(): Promise<void>
} }
@ -108,8 +109,8 @@ export interface DbHost<T = any> {
get(id: string, options?: GetOptions): Promise<Entity<T> | null> get(id: string, options?: GetOptions): Promise<Entity<T> | null>
/** Live `brain.find()` (current-generation fast path). */ /** Live `brain.find()` (current-generation fast path). */
find(query: string | FindParams<T>): Promise<Result<T>[]> find(query: string | FindParams<T>): Promise<Result<T>[]>
/** Live `brain.getRelations()` (current-generation fast path). */ /** Live `brain.related()` (current-generation fast path). */
getRelations(paramsOrId?: string | GetRelationsParams): Promise<Relation<T>[]> related(paramsOrId?: string | RelatedParams): Promise<Relation<T>[]>
/** Materialize an entity from a generation record's raw stored objects. */ /** Materialize an entity from a generation record's raw stored objects. */
entityFromRecord( entityFromRecord(
id: string, id: string,
@ -410,7 +411,7 @@ export class Db<T = any> {
/** /**
* @description Relationships **as of this view's generation** the `Db` * @description Relationships **as of this view's generation** the `Db`
* counterpart of `brain.getRelations()` (same string-id shorthand and * counterpart of `brain.related()` (same string-id shorthand and
* filter surface). Relations whose edges changed after the pin are * filter surface). Relations whose edges changed after the pin are
* resolved from generation records; overlay relations (speculative * resolved from generation records; overlay relations (speculative
* `relate`/`unrelate`) and cascade tombstones (relations touching a * `relate`/`unrelate`) and cascade tombstones (relations touching a
@ -420,18 +421,18 @@ export class Db<T = any> {
* speculative overlay it throws {@link SpeculativeOverlayError}. * speculative overlay it throws {@link SpeculativeOverlayError}.
* *
* @param paramsOrId - An entity id (shorthand for `{ from: id }`) or a * @param paramsOrId - An entity id (shorthand for `{ from: id }`) or a
* `GetRelationsParams` filter object. * `RelatedParams` filter object.
* @returns Relations as of this generation. * @returns Relations as of this generation.
*/ */
async related(paramsOrId?: string | GetRelationsParams): Promise<Relation<T>[]> { async related(paramsOrId?: string | RelatedParams): Promise<Relation<T>[]> {
this.assertUsable('related') this.assertUsable('related')
const params: GetRelationsParams = const params: RelatedParams =
typeof paramsOrId === 'string' ? { from: paramsOrId } : (paramsOrId ?? {}) typeof paramsOrId === 'string' ? { from: paramsOrId } : (paramsOrId ?? {})
const historical = this.isHistorical() const historical = this.isHistorical()
if (!historical && !this.overlay) { if (!historical && !this.overlay) {
return this.host.getRelations(paramsOrId) return this.host.related(paramsOrId)
} }
if (params.cursor !== undefined) { if (params.cursor !== undefined) {
@ -439,7 +440,7 @@ export class Db<T = any> {
throw new SpeculativeOverlayError('cursor pagination on related()', this.gen) throw new SpeculativeOverlayError('cursor pagination on related()', this.gen)
} }
const materialized = await this.materialize() const materialized = await this.materialize()
return materialized.getRelations(params) return materialized.related(params)
} }
const limit = params.limit ?? 100 const limit = params.limit ?? 100
@ -451,7 +452,7 @@ export class Db<T = any> {
const overlayVerbs = this.overlay?.verbs ?? new Map<string, Relation<T> | null>() const overlayVerbs = this.overlay?.verbs ?? new Map<string, Relation<T> | null>()
const excluded = new Set<string>([...changedVerbs, ...overlayVerbs.keys()]) const excluded = new Set<string>([...changedVerbs, ...overlayVerbs.keys()])
const base = await this.host.getRelations({ const base = await this.host.related({
...params, ...params,
limit: limit + offset + excluded.size, limit: limit + offset + excluded.size,
offset: 0 offset: 0
@ -480,7 +481,7 @@ export class Db<T = any> {
// Cascade semantics for speculative deletes: a relation whose endpoint // Cascade semantics for speculative deletes: a relation whose endpoint
// is tombstoned in the overlay is gone in this view, exactly as // is tombstoned in the overlay is gone in this view, exactly as
// brain.delete() cascades on the durable path. // brain.remove() cascades on the durable path.
if (this.overlay) { if (this.overlay) {
const tombstoned = new Set<string>() const tombstoned = new Set<string>()
for (const [id, entity] of this.overlay.nouns) { for (const [id, entity] of this.overlay.nouns) {
@ -524,8 +525,8 @@ export class Db<T = any> {
* *
* @param ops - The same declarative operations `brain.transact()` accepts. * @param ops - The same declarative operations `brain.transact()` accepts.
* @returns A new speculative `Db` over this view. * @returns A new speculative `Db` over this view.
* @throws Error when an operation references a missing entity/relation, * @throws EntityNotFoundError when an operation references a missing
* mirroring the durable path's planning errors. * entity, mirroring the durable path's planning errors.
*/ */
async with(ops: TxOperation<T>[]): Promise<Db<T>> { async with(ops: TxOperation<T>[]): Promise<Db<T>> {
this.assertUsable('with') this.assertUsable('with')
@ -587,7 +588,10 @@ export class Db<T = any> {
case 'update': { case 'update': {
const base = await speculativeGet(op.id) const base = await speculativeGet(op.id)
if (!base) { if (!base) {
throw new Error(`with(): entity ${op.id} not found at generation ${this.gen}`) throw new EntityNotFoundError(
op.id,
`with(): entity ${op.id} not found at generation ${this.gen}`
)
} }
// Same reserved-field normalization as the committed update path. // Same reserved-field normalization as the committed update path.
const { reserved, custom } = splitNounMetadataRecord( const { reserved, custom } = splitNounMetadataRecord(
@ -631,8 +635,12 @@ export class Db<T = any> {
case 'relate': { case 'relate': {
const from = await speculativeGet(op.from) const from = await speculativeGet(op.from)
const to = await speculativeGet(op.to) const to = await speculativeGet(op.to)
if (!from) throw new Error(`with(): source entity ${op.from} not found`) if (!from) {
if (!to) throw new Error(`with(): target entity ${op.to} not found`) throw new EntityNotFoundError(op.from, `with(): source entity ${op.from} not found`)
}
if (!to) {
throw new EntityNotFoundError(op.to, `with(): target entity ${op.to} not found`)
}
// Dedupe against the view (overlay first, then the edges visible // Dedupe against the view (overlay first, then the edges visible
// at this generation via the record layer) — mirror of relate(). // at this generation via the record layer) — mirror of relate().
@ -934,11 +942,11 @@ function sortResultsBy<T>(results: Result<T>[], orderBy: string, order: 'asc' |
} }
/** /**
* @description Filter one relation against `GetRelationsParams`, mirroring * @description Filter one relation against `RelatedParams`, mirroring
* the storage-level filter `brain.getRelations()` builds (`from` sourceId, * the storage-level filter `brain.related()` builds (`from` sourceId,
* `to` targetId, type/subtype set membership, service equality). * `to` targetId, type/subtype set membership, service equality).
*/ */
function relationMatchesParams<T>(relation: Relation<T>, params: GetRelationsParams): boolean { function relationMatchesParams<T>(relation: Relation<T>, params: RelatedParams): boolean {
if (params.from !== undefined && relation.from !== params.from) return false if (params.from !== undefined && relation.from !== params.from) return false
if (params.to !== undefined && relation.to !== params.to) return false if (params.to !== undefined && relation.to !== params.to) return false
if (params.type !== undefined) { if (params.type !== undefined) {

View file

@ -53,14 +53,14 @@ export interface TxUpdateOperation<T = any> extends UpdateParams<T> {
} }
/** /**
* @description Delete an entity (and, exactly like `brain.delete()`, every * @description Remove an entity (and, exactly like `brain.remove()`, every
* relationship where it is source or target the cascade is part of the * relationship where it is source or target the cascade is part of the
* same atomic batch). * same atomic batch).
*/ */
export interface TxRemoveOperation { export interface TxRemoveOperation {
/** Discriminator. */ /** Discriminator. */
op: 'remove' op: 'remove'
/** Id of the entity to delete. */ /** Id of the entity to remove. */
id: string id: string
} }

View file

@ -124,11 +124,12 @@ export class DistributedCoordinator extends EventEmitter {
private setupNetworkHandlers(): void { private setupNetworkHandlers(): void {
if (!this.networkTransport) return if (!this.networkTransport) return
// Register message handlers directly on the messageHandlers map // Register handlers through the transport's public onMessage(), which
const handlers = (this.networkTransport as any).messageHandlers as Map<string, (msg: NetworkMessage) => Promise<any>> // writes to the same messageHandlers map this code used to reach into.
const transport = this.networkTransport
// Handle vote requests // Handle vote requests
handlers.set('requestVote', async (msg: NetworkMessage) => { transport.onMessage('requestVote', async (msg: NetworkMessage) => {
const response = await this.handleVoteRequest(msg) const response = await this.handleVoteRequest(msg)
// Send response back // Send response back
if (this.networkTransport) { if (this.networkTransport) {
@ -138,12 +139,12 @@ export class DistributedCoordinator extends EventEmitter {
}) })
// Handle vote responses // Handle vote responses
handlers.set('voteResponse', async (msg: NetworkMessage) => { transport.onMessage('voteResponse', async (msg: NetworkMessage) => {
this.handleVoteResponse(msg) this.handleVoteResponse(msg)
}) })
// Handle heartbeats/append entries // Handle heartbeats/append entries
handlers.set('appendEntries', async (msg: NetworkMessage) => { transport.onMessage('appendEntries', async (msg: NetworkMessage) => {
const response = await this.handleAppendEntries(msg) const response = await this.handleAppendEntries(msg)
// Send response back // Send response back
if (this.networkTransport) { if (this.networkTransport) {
@ -153,7 +154,7 @@ export class DistributedCoordinator extends EventEmitter {
}) })
// Handle append responses // Handle append responses
handlers.set('appendResponse', async (msg: NetworkMessage) => { transport.onMessage('appendResponse', async (msg: NetworkMessage) => {
this.handleAppendResponse(msg) this.handleAppendResponse(msg)
}) })
} }

View file

@ -75,7 +75,12 @@ export class NetworkTransport extends EventEmitter {
if (this.isRunning) return if (this.isRunning) return
const ws = await import('ws') const ws = await import('ws')
WebSocketServer = (ws as any).WebSocketServer || (ws as any).Server || ws.default // The ws package has exported its server class as `WebSocketServer`
// (v8+), `Server` (older releases) and via the default export — and the
// available type declarations disagree on which of those are value
// exports, so probe all three through an optional-keyed module view.
const wsModule = ws as typeof ws & { WebSocketServer?: unknown; Server?: unknown }
WebSocketServer = wsModule.WebSocketServer || wsModule.Server || ws.default
await this.startHTTPServer() await this.startHTTPServer()
await this.startWebSocketServer() await this.startWebSocketServer()

View file

@ -6,7 +6,7 @@
import { EventEmitter } from 'node:events' import { EventEmitter } from 'node:events'
import * as os from 'node:os' import * as os from 'node:os'
import { StorageAdapter } from '../coreTypes.js' import { NounMetadata, StorageAdapter } from '../coreTypes.js'
export interface NodeInfo { export interface NodeInfo {
id: string id: string
@ -357,7 +357,11 @@ export class StorageDiscovery extends EventEmitter {
*/ */
private async loadNodeRegistry(): Promise<string[]> { private async loadNodeRegistry(): Promise<string[]> {
try { try {
const registry = await this.storage.getMetadata(`${this.CLUSTER_PATH}/registry.json`) as any // The registry document is written by updateNodeRegistry() below with a
// `nodes` string-ID array on top of the base NounMetadata shape.
const registry = await this.storage.getMetadata(
`${this.CLUSTER_PATH}/registry.json`
) as (NounMetadata & { nodes?: string[] }) | null
return registry?.nodes || [] return registry?.nodes || []
} catch (err) { } catch (err) {
return [] return []

View file

@ -15,6 +15,17 @@
import { Vector, EmbeddingFunction } from '../coreTypes.js' import { Vector, EmbeddingFunction } from '../coreTypes.js'
import { WASMEmbeddingEngine } from './wasm/index.js' import { WASMEmbeddingEngine } from './wasm/index.js'
declare global {
/**
* Unit-test escape hatch set by the test harness (tests/setup-unit.ts):
* when truthy, EmbeddingManager serves deterministic mock embeddings
* instead of loading the real model. Guarded against production use in
* init()/embed()/embedBatch(). Ambient `var` is required here `let`/
* `const` in `declare global` do not attach to `globalThis`.
*/
var __BRAINY_UNIT_TEST__: boolean | undefined
}
// Types // Types
export type ModelPrecision = 'q8' | 'fp32' export type ModelPrecision = 'q8' | 'fp32'
@ -68,7 +79,7 @@ export class EmbeddingManager {
// In unit test mode, skip real model initialization // In unit test mode, skip real model initialization
const isTestMode = const isTestMode =
process.env.BRAINY_UNIT_TEST === 'true' || process.env.BRAINY_UNIT_TEST === 'true' ||
(globalThis as any).__BRAINY_UNIT_TEST__ globalThis.__BRAINY_UNIT_TEST__
if (isTestMode) { if (isTestMode) {
// Production safeguard // Production safeguard
@ -142,7 +153,7 @@ export class EmbeddingManager {
// Check for unit test environment // Check for unit test environment
const isTestMode = const isTestMode =
process.env.BRAINY_UNIT_TEST === 'true' || process.env.BRAINY_UNIT_TEST === 'true' ||
(globalThis as any).__BRAINY_UNIT_TEST__ globalThis.__BRAINY_UNIT_TEST__
if (isTestMode) { if (isTestMode) {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
@ -224,7 +235,7 @@ export class EmbeddingManager {
const isTestMode = const isTestMode =
process.env.BRAINY_UNIT_TEST === 'true' || process.env.BRAINY_UNIT_TEST === 'true' ||
(globalThis as any).__BRAINY_UNIT_TEST__ globalThis.__BRAINY_UNIT_TEST__
if (isTestMode) { if (isTestMode) {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {

79
src/errors/notFound.ts Normal file
View file

@ -0,0 +1,79 @@
/**
* @module errors/notFound
* @description Named not-found errors for Brainy's public contract.
*
* Operations that require an existing record `update()`, `relate()`,
* `updateRelation()`, `similar({ to: id })`, `transact()` planning, and
* speculative `db.with()` planning throw these instead of a generic
* `Error`, so callers can branch with `instanceof` and read the missing
* `id` programmatically instead of parsing message strings:
*
* - {@link EntityNotFoundError} a referenced entity (noun) does not exist
* in the store (or, for `db.with()`, is not visible at the pinned
* generation).
* - {@link RelationNotFoundError} a referenced relationship (verb) does
* not exist.
*
* Both are exported from the package root (`@soulcraft/brainy`).
*/
/**
* @description Thrown when an operation references an entity id that does
* not exist. Carries the missing {@link EntityNotFoundError.id} so callers
* can recover (re-create, skip, or surface it) without string matching.
*
* @example
* try {
* await brain.update({ id, metadata: { status: 'active' } })
* } catch (err) {
* if (err instanceof EntityNotFoundError) {
* console.log(`missing entity: ${err.id}`)
* }
* }
*/
export class EntityNotFoundError extends Error {
/** The entity id that could not be found. */
public readonly id: string
/**
* @param id - The entity id that could not be found.
* @param message - Optional message override for call sites that add
* context (e.g. source/target role, pinned generation). Defaults to
* `Entity <id> not found`.
*/
constructor(id: string, message?: string) {
super(message ?? `Entity ${id} not found`)
this.name = 'EntityNotFoundError'
this.id = id
}
}
/**
* @description Thrown when an operation references a relationship id that
* does not exist. Carries the missing {@link RelationNotFoundError.id} so
* callers can recover without string matching.
*
* @example
* try {
* await brain.updateRelation({ id: relId, subtype: 'dotted-line' })
* } catch (err) {
* if (err instanceof RelationNotFoundError) {
* console.log(`missing relation: ${err.id}`)
* }
* }
*/
export class RelationNotFoundError extends Error {
/** The relationship id that could not be found. */
public readonly id: string
/**
* @param id - The relationship id that could not be found.
* @param message - Optional message override for call sites that add
* context. Defaults to `Relation <id> not found`.
*/
constructor(id: string, message?: string) {
super(message ?? `Relation ${id} not found`)
this.name = 'RelationNotFoundError'
this.id = id
}
}

View file

@ -570,9 +570,9 @@ export class GraphAdjacencyIndex implements GraphIndexProvider {
for (const [verbId, verb] of loadedVerbs.entries()) { for (const [verbId, verb] of loadedVerbs.entries()) {
const cacheKey = `graph:verb:${verbId}` const cacheKey = `graph:verb:${verbId}`
// Cache the loaded verb with metadata // Cache the loaded verb with metadata
// Note: HNSWVerbWithMetadata is compatible with GraphVerb (both interfaces) // Note: HNSWVerbWithMetadata is structurally assignable to GraphVerb
this.unifiedCache.set(cacheKey, verb as any, 'other', 128, 50) // 128 bytes estimated size, 50ms rebuild cost this.unifiedCache.set(cacheKey, verb, 'other', 128, 50) // 128 bytes estimated size, 50ms rebuild cost
results.set(verbId, verb as any) results.set(verbId, verb)
} }
} }

View file

@ -145,6 +145,26 @@ class MemTable {
/** /**
* Manifest - Tracks all SSTables and their levels * Manifest - Tracks all SSTables and their levels
*/ */
/**
* Persisted manifest payload as written by `saveManifest()` (the `data` field
* of the manifest metadata record, after a JSON round-trip).
*/
type PersistedManifestData = {
sstables?: Record<string, number>
lastCompaction?: number
totalRelationships?: number
}
/**
* Persisted SSTable payload as written by `flushMemTable()`/`compact()` (the
* `data` field of an SSTable metadata record): serialized bytes stored as a
* plain number array so they survive JSON round-trips.
*/
type PersistedSSTableData = {
type: string
data: number[]
}
interface Manifest { interface Manifest {
/** /**
* Map of SSTable ID to level * Map of SSTable ID to level
@ -512,10 +532,12 @@ export class LSMTree {
const metadata = await this.storage.getMetadata(`${this.config.storagePrefix}-manifest`) const metadata = await this.storage.getMetadata(`${this.config.storagePrefix}-manifest`)
if (metadata && metadata.data) { if (metadata && metadata.data) {
const data = metadata.data as any // Storage boundary: `data` is the JSON manifest payload written by
// saveManifest(); re-typed from the metadata channel's `unknown`.
const data = metadata.data as PersistedManifestData
this.manifest.sstables = new Map(Object.entries(data.sstables || {})) this.manifest.sstables = new Map(Object.entries(data.sstables || {}))
this.manifest.lastCompaction = (data.lastCompaction as number) || Date.now() this.manifest.lastCompaction = data.lastCompaction || Date.now()
this.manifest.totalRelationships = (data.totalRelationships as number) || 0 this.manifest.totalRelationships = data.totalRelationships || 0
// Load SSTables from storage // Load SSTables from storage
await this.loadSSTables() await this.loadSSTables()
@ -538,7 +560,9 @@ export class LSMTree {
const metadata = await this.storage.getMetadata(storageKey) const metadata = await this.storage.getMetadata(storageKey)
if (metadata && metadata.data) { if (metadata && metadata.data) {
const data = metadata.data as any // Storage boundary: `data` is the JSON SSTable payload written by
// flushMemTable()/compact(); re-typed from `unknown`.
const data = metadata.data as PersistedSSTableData
if (data.type === 'lsm-sstable') { if (data.type === 'lsm-sstable') {
// Convert number[] back to Uint8Array // Convert number[] back to Uint8Array
const uint8Data = new Uint8Array(data.data) const uint8Data = new Uint8Array(data.data)

View file

@ -1228,7 +1228,7 @@ export class JsHnswVectorIndex implements VectorIndexProvider {
this.clear() this.clear()
// Step 2: Load system data (entry point, max level) // Step 2: Load system data (entry point, max level)
const systemData = await (this.storage as any).getHNSWSystem() const systemData = await this.storage.getHNSWSystem()
if (systemData) { if (systemData) {
this.entryPointId = systemData.entryPointId this.entryPointId = systemData.entryPointId
this.maxLevel = systemData.maxLevel this.maxLevel = systemData.maxLevel
@ -1270,13 +1270,9 @@ export class JsHnswVectorIndex implements VectorIndexProvider {
{ {
prodLog.info(`HNSW: Load all nodes at once (${storageType})`) prodLog.info(`HNSW: Load all nodes at once (${storageType})`)
const result: { const result = await this.storage.getNounsWithPagination({
items: HNSWNoun[] limit: 10000000, // Effectively unlimited for local development
totalCount?: number offset: 0
hasMore: boolean
nextCursor?: string
} = await (this.storage as any).getNounsWithPagination({
limit: 10000000 // Effectively unlimited for local development
}) })
totalCount = result.totalCount || result.items.length totalCount = result.totalCount || result.items.length
@ -1285,7 +1281,7 @@ export class JsHnswVectorIndex implements VectorIndexProvider {
for (const nounData of result.items) { for (const nounData of result.items) {
try { try {
// Load HNSW graph data for this entity // Load HNSW graph data for this entity
const hnswData = await (this.storage as any).getVectorIndexData(nounData.id) const hnswData = await this.storage.getVectorIndexData(nounData.id)
if (!hnswData) { if (!hnswData) {
// No HNSW data - skip (might be entity added before persistence) // No HNSW data - skip (might be entity added before persistence)

View file

@ -123,7 +123,7 @@ export class BackgroundDeduplicator {
try { try {
// Get all entities from this import using brain.find() // Get all entities from this import using brain.find()
const results = await this.brain.find({ const results = await this.brain.find({
where: { importId } as any, where: { importId },
limit: 100000 // Large limit to get all entities from import limit: 100000 // Large limit to get all entities from import
}) })
@ -298,7 +298,7 @@ export class BackgroundDeduplicator {
return this.brain.find({ return this.brain.find({
query, query,
limit: 5, limit: 5,
where: { type: entity.type } as any // Type-aware search where: { type: entity.type } // Type-aware search
}) })
}) })
@ -318,8 +318,11 @@ export class BackgroundDeduplicator {
// Check if not already merged // Check if not already merged
const stillExists = await this.brain.get(entity.id) const stillExists = await this.brain.get(entity.id)
if (stillExists) { if (stillExists) {
// Cast match.entity to HNSWNounWithMetadata (it comes from brain.find results) // Typed boundary: bridge the public Entity<T> result shape from
const matchEntity = match.entity as any as HNSWNounWithMetadata // brain.find() to the storage-layer HNSWNounWithMetadata record
// (same structural bridge as the results.map above). The merge
// path only reads id/type/metadata, present in both shapes.
const matchEntity = match.entity as unknown as HNSWNounWithMetadata
await this.mergeEntities([entity, matchEntity], 'Similarity') await this.mergeEntities([entity, matchEntity], 'Similarity')
merged++ merged++
break // Only merge with first high-similarity match break // Only merge with first high-similarity match
@ -388,7 +391,7 @@ export class BackgroundDeduplicator {
for (const entity of entities) { for (const entity of entities) {
if (entity.id !== primary.id) { if (entity.id !== primary.id) {
try { try {
await this.brain.delete(entity.id) await this.brain.remove(entity.id)
} catch (error) { } catch (error) {
// Entity might already be deleted, continue // Entity might already be deleted, continue
prodLog.debug(`[BackgroundDedup] Could not delete ${entity.id}:`, error) prodLog.debug(`[BackgroundDedup] Could not delete ${entity.id}:`, error)

View file

@ -88,7 +88,7 @@ export class EntityDeduplicator {
const results = await this.brain.find({ const results = await this.brain.find({
query: searchText, query: searchText,
limit: 5, limit: 5,
where: opts.strictTypeMatching ? { type: candidate.type } as any : undefined where: opts.strictTypeMatching ? { type: candidate.type } : undefined
}) })
// Check each result for potential duplicates // Check each result for potential duplicates
@ -227,7 +227,9 @@ export class EntityDeduplicator {
const entityId = await this.brain.add({ const entityId = await this.brain.add({
data: candidate.description || candidate.name, data: candidate.description || candidate.name,
type: candidate.type, type: candidate.type,
subtype: (candidate as any).subtype ?? 'imported', subtype:
(candidate as EntityCandidate & { subtype?: string }).subtype ??
'imported',
confidence: candidate.confidence, confidence: candidate.confidence,
metadata: { metadata: {
...candidate.metadata, ...candidate.metadata,

View file

@ -12,7 +12,7 @@
import { Brainy } from '../brainy.js' import { Brainy } from '../brainy.js'
import { FormatDetector, SupportedFormat } from './FormatDetector.js' import { FormatDetector, SupportedFormat } from './FormatDetector.js'
import { ImportHistory } from './ImportHistory.js' import { ImportHistory, type ImportHistoryEntry } from './ImportHistory.js'
import { BackgroundDeduplicator } from './BackgroundDeduplicator.js' import { BackgroundDeduplicator } from './BackgroundDeduplicator.js'
import { SmartExcelImporter } from '../importers/SmartExcelImporter.js' import { SmartExcelImporter } from '../importers/SmartExcelImporter.js'
import { SmartPDFImporter } from '../importers/SmartPDFImporter.js' import { SmartPDFImporter } from '../importers/SmartPDFImporter.js'
@ -489,7 +489,13 @@ export class ImportCoordinator {
await this.history.recordImport( await this.history.recordImport(
importId, importId,
{ {
type: normalizedSource.type === 'path' ? 'file' : normalizedSource.type as any, // 'url' sources were already converted to 'buffer' by
// normalizeSource() → fetchUrl(), so only history-recordable
// source types remain here after the 'path' → 'file' mapping.
type:
normalizedSource.type === 'path'
? 'file'
: (normalizedSource.type as ImportHistoryEntry['source']['type']),
filename: normalizedSource.filename, filename: normalizedSource.filename,
format: detection.format format: detection.format
}, },
@ -699,11 +705,25 @@ export class ImportCoordinator {
format: SupportedFormat, format: SupportedFormat,
options: ImportOptions options: ImportOptions
): Promise<any> { ): Promise<any> {
// Check if IntelligentImportAugmentation already extracted data // Check if IntelligentImportAugmentation already extracted data.
if ((options as any)._intelligentImport && (options as any)._extractedData) { // Typed boundary: the intelligent-import pre-processing path smuggles its
const extractedData = (options as any)._extractedData // results onto ImportOptions via these underscore-prefixed private fields —
// they are not part of the public ImportOptions contract.
const intelligentOptions = options as ImportOptions & {
_intelligentImport?: boolean
_extractedData?: Array<{
id?: string
name?: string
type?: string
description?: string
metadata?: Record<string, unknown>
}>
_metadata?: { intelligentImport?: Record<string, unknown> }
}
if (intelligentOptions._intelligentImport && intelligentOptions._extractedData) {
const extractedData = intelligentOptions._extractedData
// Convert extracted data to ExtractedRow format // Convert extracted data to ExtractedRow format
const rows = extractedData.map((item: any) => ({ const rows = extractedData.map((item) => ({
entity: { entity: {
id: item.id || `entity-${Date.now()}-${Math.random()}`, id: item.id || `entity-${Date.now()}-${Math.random()}`,
name: item.name || item.type || 'Unnamed', name: item.name || item.type || 'Unnamed',
@ -719,7 +739,7 @@ export class ImportCoordinator {
rows, rows,
entities: extractedData, entities: extractedData,
relationships: [], relationships: [],
metadata: (options as any)._metadata?.intelligentImport || {}, metadata: intelligentOptions._metadata?.intelligentImport || {},
stats: { stats: {
byType: {}, byType: {},
byConfidence: {} byConfidence: {}
@ -811,7 +831,7 @@ export class ImportCoordinator {
entity: { entity: {
id: imageId, id: imageId,
name: imageName, name: imageName,
type: 'media' as any, type: 'media',
description: '', description: '',
confidence: 1.0, confidence: 1.0,
metadata: { subtype: 'image' } metadata: { subtype: 'image' }
@ -864,7 +884,18 @@ export class ImportCoordinator {
provenanceCount?: number provenanceCount?: number
}> { }> {
const entities: Array<{ id: string; name: string; type: NounType; vfsPath?: string; metadata?: Record<string, any> }> = [] const entities: Array<{ id: string; name: string; type: NounType; vfsPath?: string; metadata?: Record<string, any> }> = []
const relationships: Array<{ id: string; from: string; to: string; type: VerbType }> = [] // Wider than the declared return type: confidence/weight/metadata are
// collected per relationship for the relateMany() batch below; callers of
// createGraphEntities() only see the narrower {id, from, to, type} rows.
const relationships: Array<{
id: string
from: string
to: string
type: VerbType
confidence?: number
weight?: number
metadata?: { evidence?: string; [key: string]: unknown }
}> = []
let mergedCount = 0 let mergedCount = 0
let newCount = 0 let newCount = 0
@ -1227,7 +1258,7 @@ export class ImportCoordinator {
...trackingContext.customMetadata ...trackingContext.customMetadata
}) })
} }
} as any) })
} catch (error) { } catch (error) {
// Skip relationship collection errors (entity might not exist, etc.) // Skip relationship collection errors (entity might not exist, etc.)
continue continue
@ -1314,7 +1345,7 @@ export class ImportCoordinator {
verbType = this.inferRelationshipType( verbType = this.inferRelationshipType(
sourceEntity.type, sourceEntity.type,
targetEntity.type, targetEntity.type,
(rel as any).metadata?.evidence rel.metadata?.evidence
) )
} }
@ -1323,7 +1354,7 @@ export class ImportCoordinator {
to: rel.to, to: rel.to,
type: verbType, // Enhanced type type: verbType, // Enhanced type
metadata: { metadata: {
...((rel as any).metadata || {}), ...(rel.metadata || {}),
relationshipType: 'semantic', // Distinguish from VFS/provenance relationshipType: 'semantic', // Distinguish from VFS/provenance
inferredType: verbType !== rel.type, // Track if type was enhanced inferredType: verbType !== rel.type, // Track if type was enhanced
originalType: rel.type originalType: rel.type

View file

@ -165,7 +165,7 @@ export class ImportHistory {
// Delete entities // Delete entities
for (const entityId of entry.entities) { for (const entityId of entry.entities) {
try { try {
await this.brain.delete(entityId) await this.brain.remove(entityId)
result.entitiesDeleted++ result.entitiesDeleted++
} catch (error) { } catch (error) {
result.errors.push(`Failed to delete entity ${entityId}: ${error instanceof Error ? error.message : String(error)}`) result.errors.push(`Failed to delete entity ${entityId}: ${error instanceof Error ? error.message : String(error)}`)

View file

@ -164,7 +164,9 @@ export class SmartCSVImporter {
relatedColumn: 'related|see also|links|references', relatedColumn: 'related|see also|links|references',
csvDelimiter: undefined as string | undefined, csvDelimiter: undefined as string | undefined,
csvHeaders: true, csvHeaders: true,
onProgress: () => {}, // Annotated with the full callback signature so the merged opts type has
// a single callable shape (the no-op default narrowed the union).
onProgress: (() => {}) as NonNullable<SmartCSVOptions['onProgress']>,
...options ...options
} }
@ -379,7 +381,7 @@ export class SmartCSVImporter {
throughput: Math.round(rowsPerSecond * 10) / 10, throughput: Math.round(rowsPerSecond * 10) / 10,
eta: Math.round(estimatedTimeRemaining), eta: Math.round(estimatedTimeRemaining),
phase: 'extracting' phase: 'extracting'
} as any) })
} }
return { return {

View file

@ -161,15 +161,9 @@ export class SmartExcelImporter {
const opts = { const opts = {
enableNeuralExtraction: true, enableNeuralExtraction: true,
enableRelationshipInference: true, enableRelationshipInference: true,
// CONCEPT EXTRACTION PRODUCTION-READY: // Type embeddings are pre-computed at build time (~100KB in-memory),
// Type embeddings are now pre-computed at build time - zero runtime cost! // so concept extraction costs no runtime embedding calls: model loading
// All 42 noun types + 127 verb types instantly available // is a one-time ~2-5s, then ~50-200ms per row.
//
// Performance profile:
// - Type embeddings: INSTANT (pre-computed at build time, ~100KB in-memory)
// - Model loading: ~2-5 seconds (one-time, cached after first use)
// - Per-row extraction: ~50-200ms depending on definition length
// - 100 rows: ~5-20 seconds total (production ready)
// - 1000 rows: ~50-200 seconds (disable if needed via enableConceptExtraction: false) // - 1000 rows: ~50-200 seconds (disable if needed via enableConceptExtraction: false)
// //
// Enabled by default for production use. // Enabled by default for production use.
@ -179,7 +173,9 @@ export class SmartExcelImporter {
definitionColumn: 'definition|description|desc|details', definitionColumn: 'definition|description|desc|details',
typeColumn: 'type|category|kind', typeColumn: 'type|category|kind',
relatedColumn: 'related|see also|links', relatedColumn: 'related|see also|links',
onProgress: () => {}, // Annotated with the full callback signature so the merged opts type has
// a single callable shape (the no-op default narrowed the union).
onProgress: (() => {}) as NonNullable<SmartExcelOptions['onProgress']>,
...options ...options
} }
@ -489,7 +485,7 @@ export class SmartExcelImporter {
throughput: Math.round(rowsPerSecond * 10) / 10, throughput: Math.round(rowsPerSecond * 10) / 10,
eta: Math.round(estimatedTimeRemaining), eta: Math.round(estimatedTimeRemaining),
phase: 'extracting' phase: 'extracting'
} as any) })
} }
// Group rows by sheet for VFS extraction // Group rows by sheet for VFS extraction

View file

@ -133,7 +133,10 @@ export class SmartImportOrchestrator {
const startTime = Date.now() const startTime = Date.now()
const result: SmartImportResult = { const result: SmartImportResult = {
success: false, success: false,
extraction: null as any, // Typed boundary: populated in the extraction phase below. If extraction
// throws, the error path returns with this still null (pre-existing
// contract — consumers check `success`/`errors` before reading it).
extraction: null as unknown as SmartExcelResult,
entityIds: [], entityIds: [],
relationshipIds: [], relationshipIds: [],
stats: { stats: {
@ -193,7 +196,9 @@ export class SmartImportOrchestrator {
const entityId = await this.brain.add({ const entityId = await this.brain.add({
data: extracted.entity.description, data: extracted.entity.description,
type: extracted.entity.type, type: extracted.entity.type,
subtype: (extracted.entity as any).subtype ?? options.defaultSubtype ?? 'imported', subtype:
(extracted.entity as typeof extracted.entity & { subtype?: string })
.subtype ?? options.defaultSubtype ?? 'imported',
confidence: extracted.entity.confidence, // reserved field — dedicated param, not metadata confidence: extracted.entity.confidence, // reserved field — dedicated param, not metadata
metadata: { metadata: {
...extracted.entity.metadata, ...extracted.entity.metadata,
@ -280,7 +285,9 @@ export class SmartImportOrchestrator {
from: extracted.entity.id, from: extracted.entity.id,
to: toEntityId, to: toEntityId,
type: rel.type, type: rel.type,
subtype: (rel as any).subtype ?? options.defaultSubtype ?? 'imported', subtype:
(rel as typeof rel & { subtype?: string }).subtype ??
options.defaultSubtype ?? 'imported',
metadata: { metadata: {
confidence: rel.confidence, confidence: rel.confidence,
evidence: rel.evidence evidence: rel.evidence
@ -393,7 +400,8 @@ export class SmartImportOrchestrator {
const startTime = Date.now() const startTime = Date.now()
const result: SmartImportResult = { const result: SmartImportResult = {
success: false, success: false,
extraction: null as any, // Typed boundary: populated after extraction (see importExcel).
extraction: null as unknown as SmartExcelResult,
entityIds: [], entityIds: [],
relationshipIds: [], relationshipIds: [],
stats: { stats: {
@ -413,7 +421,7 @@ export class SmartImportOrchestrator {
const pdfResult = await this.pdfImporter.extract(buffer, options) const pdfResult = await this.pdfImporter.extract(buffer, options)
// Convert PDF result to Excel-like format for processing // Convert PDF result to Excel-like format for processing
result.extraction = this.convertPDFToExcelFormat(pdfResult) as any result.extraction = this.convertPDFToExcelFormat(pdfResult)
result.stats.rowsProcessed = pdfResult.sectionsProcessed result.stats.rowsProcessed = pdfResult.sectionsProcessed
// Phase 2 & 3: Create entities and relationships // Phase 2 & 3: Create entities and relationships
@ -470,7 +478,8 @@ export class SmartImportOrchestrator {
const startTime = Date.now() const startTime = Date.now()
const result: SmartImportResult = { const result: SmartImportResult = {
success: false, success: false,
extraction: null as any, // Typed boundary: populated after extraction (see importExcel).
extraction: null as unknown as SmartExcelResult,
entityIds: [], entityIds: [],
relationshipIds: [], relationshipIds: [],
stats: { stats: {
@ -488,7 +497,7 @@ export class SmartImportOrchestrator {
const jsonResult = await this.jsonImporter.extract(data, options) const jsonResult = await this.jsonImporter.extract(data, options)
result.extraction = this.convertJSONToExcelFormat(jsonResult) as any result.extraction = this.convertJSONToExcelFormat(jsonResult)
result.stats.rowsProcessed = jsonResult.nodesProcessed result.stats.rowsProcessed = jsonResult.nodesProcessed
await this.createEntitiesAndRelationships(result, options, onProgress) await this.createEntitiesAndRelationships(result, options, onProgress)
@ -532,7 +541,8 @@ export class SmartImportOrchestrator {
const startTime = Date.now() const startTime = Date.now()
const result: SmartImportResult = { const result: SmartImportResult = {
success: false, success: false,
extraction: null as any, // Typed boundary: populated after extraction (see importExcel).
extraction: null as unknown as SmartExcelResult,
entityIds: [], entityIds: [],
relationshipIds: [], relationshipIds: [],
stats: { stats: {
@ -550,7 +560,7 @@ export class SmartImportOrchestrator {
const mdResult = await this.markdownImporter.extract(markdown, options) const mdResult = await this.markdownImporter.extract(markdown, options)
result.extraction = this.convertMarkdownToExcelFormat(mdResult) as any result.extraction = this.convertMarkdownToExcelFormat(mdResult)
result.stats.rowsProcessed = mdResult.sectionsProcessed result.stats.rowsProcessed = mdResult.sectionsProcessed
await this.createEntitiesAndRelationships(result, options, onProgress) await this.createEntitiesAndRelationships(result, options, onProgress)
@ -601,7 +611,9 @@ export class SmartImportOrchestrator {
const entityId = await this.brain.add({ const entityId = await this.brain.add({
data: extracted.entity.description, data: extracted.entity.description,
type: extracted.entity.type, type: extracted.entity.type,
subtype: (extracted.entity as any).subtype ?? options.defaultSubtype ?? 'imported', subtype:
(extracted.entity as typeof extracted.entity & { subtype?: string })
.subtype ?? options.defaultSubtype ?? 'imported',
confidence: extracted.entity.confidence, // reserved field — dedicated param, not metadata confidence: extracted.entity.confidence, // reserved field — dedicated param, not metadata
metadata: { ...extracted.entity.metadata, name: extracted.entity.name, importedFrom: 'smart-import' } metadata: { ...extracted.entity.metadata, name: extracted.entity.name, importedFrom: 'smart-import' }
}) })
@ -637,7 +649,7 @@ export class SmartImportOrchestrator {
} }
// Relationship subtype precedence: extractor → caller default → `'imported'` (7.30.1). // Relationship subtype precedence: extractor → caller default → `'imported'` (7.30.1).
// `confidence` is a reserved top-level field — dedicated relate() param, not metadata // `confidence` is a reserved top-level field — dedicated relate() param, not metadata
relationshipParams.push({ from: extracted.entity.id, to: toEntityId, type: rel.type, subtype: (rel as any).subtype ?? options.defaultSubtype ?? 'imported', confidence: rel.confidence, metadata: { evidence: rel.evidence } }) relationshipParams.push({ from: extracted.entity.id, to: toEntityId, type: rel.type, subtype: (rel as typeof rel & { subtype?: string }).subtype ?? options.defaultSubtype ?? 'imported', confidence: rel.confidence, metadata: { evidence: rel.evidence } })
} catch (error: any) { } catch (error: any) {
result.errors.push(`Failed to prepare relationship: ${error.message}`) result.errors.push(`Failed to prepare relationship: ${error.message}`)
} }
@ -688,7 +700,7 @@ export class SmartImportOrchestrator {
rows, rows,
entityMap: pdfResult.entityMap, entityMap: pdfResult.entityMap,
processingTime: pdfResult.processingTime, processingTime: pdfResult.processingTime,
stats: pdfResult.stats as any stats: pdfResult.stats
} }
} }
@ -710,7 +722,7 @@ export class SmartImportOrchestrator {
rows, rows,
entityMap: jsonResult.entityMap, entityMap: jsonResult.entityMap,
processingTime: jsonResult.processingTime, processingTime: jsonResult.processingTime,
stats: jsonResult.stats as any stats: jsonResult.stats
} }
} }
@ -734,7 +746,7 @@ export class SmartImportOrchestrator {
rows, rows,
entityMap: mdResult.entityMap, entityMap: mdResult.entityMap,
processingTime: mdResult.processingTime, processingTime: mdResult.processingTime,
stats: mdResult.stats as any stats: mdResult.stats
} }
} }

View file

@ -176,7 +176,9 @@ export class SmartPDFImporter {
minParagraphLength: 50, minParagraphLength: 50,
extractFromTables: true, extractFromTables: true,
groupBy: 'document' as const, groupBy: 'document' as const,
onProgress: () => {}, // Annotated with the full callback signature so the merged opts type has
// a single callable shape (the no-op default narrowed the union).
onProgress: (() => {}) as NonNullable<SmartPDFOptions['onProgress']>,
...options ...options
} }
@ -274,7 +276,7 @@ export class SmartPDFImporter {
throughput: Math.round(sectionsPerSecond * 10) / 10, throughput: Math.round(sectionsPerSecond * 10) / 10,
eta: Math.round(estimatedTimeRemaining), eta: Math.round(estimatedTimeRemaining),
phase: 'extracting' phase: 'extracting'
} as any) })
} }
const pagesProcessed = new Set(data.map(d => d._page)).size const pagesProcessed = new Set(data.map(d => d._page)).size
@ -338,7 +340,7 @@ export class SmartPDFImporter {
* Process a single section * Process a single section
*/ */
private async processSection( private async processSection(
group: { id: string, type: string, items: Array<Record<string, any>> }, group: { id: string, type: 'page' | 'paragraph' | 'table', items: Array<Record<string, any>> },
options: SmartPDFOptions, options: SmartPDFOptions,
stats: SmartPDFResult['stats'], stats: SmartPDFResult['stats'],
entityMap: Map<string, string> entityMap: Map<string, string>
@ -444,7 +446,7 @@ export class SmartPDFImporter {
return { return {
sectionId: group.id, sectionId: group.id,
sectionType: group.type as any, sectionType: group.type,
entities, entities,
relationships, relationships,
concepts, concepts,

View file

@ -69,13 +69,14 @@ export class ExcelHandler extends BaseFormatHandler {
const sheet = workbook.Sheets[sheetName] const sheet = workbook.Sheets[sheetName]
if (!sheet) continue if (!sheet) continue
// Convert sheet to JSON with headers // Convert sheet to JSON with headers. `header: 1` makes xlsx return
const sheetData = XLSX.utils.sheet_to_json(sheet, { // rows as arrays of raw cell values, typed via the generic overload.
const sheetData = XLSX.utils.sheet_to_json<unknown[]>(sheet, {
header: 1, // Get as array of arrays first header: 1, // Get as array of arrays first
defval: null, defval: null,
blankrows: false, blankrows: false,
raw: false // Convert to formatted strings raw: false // Convert to formatted strings
}) as any[][] })
if (sheetData.length === 0) continue if (sheetData.length === 0) continue

View file

@ -70,8 +70,20 @@ export class PDFHandler extends BaseFormatHandler {
const pdfDoc = await loadingTask.promise const pdfDoc = await loadingTask.promise
// Extract metadata // Extract metadata. pdfjs-dist types getMetadata().info as the bare
// `Object` type, so the well-known PDF document-information dictionary
// keys (PDF 32000-1:2008 §14.3.3) are surfaced via a structural type at
// this library boundary.
const metadata = await pdfDoc.getMetadata() const metadata = await pdfDoc.getMetadata()
const documentInfo = metadata.info as {
Title?: string
Author?: string
Subject?: string
Creator?: string
Producer?: string
CreationDate?: string
ModDate?: string
} | undefined
const numPages = pdfDoc.numPages const numPages = pdfDoc.numPages
// Report document loaded // Report document loaded
@ -177,13 +189,13 @@ export class PDFHandler extends BaseFormatHandler {
textLength: totalTextLength, textLength: totalTextLength,
tableCount: detectedTables, tableCount: detectedTables,
pdfMetadata: { pdfMetadata: {
title: (metadata.info as any)?.Title || null, title: documentInfo?.Title || null,
author: (metadata.info as any)?.Author || null, author: documentInfo?.Author || null,
subject: (metadata.info as any)?.Subject || null, subject: documentInfo?.Subject || null,
creator: (metadata.info as any)?.Creator || null, creator: documentInfo?.Creator || null,
producer: (metadata.info as any)?.Producer || null, producer: documentInfo?.Producer || null,
creationDate: (metadata.info as any)?.CreationDate || null, creationDate: documentInfo?.CreationDate || null,
modificationDate: (metadata.info as any)?.ModDate || null modificationDate: documentInfo?.ModDate || null
} }
} }
), ),

View file

@ -1,15 +1,16 @@
/** /**
* Brainy 3.0 - Your AI-Powered Second Brain * Brainy 8.0 - Your AI-Powered Second Brain
* 🧠 A multi-dimensional database with vector, graph, and relational storage * 🧠 A multi-dimensional database with vector, graph, and relational storage
* *
* Core Components: * Core Components:
* - Brainy: The unified database with Triple Intelligence * - Brainy: The unified database with Triple Intelligence
* - Triple Intelligence: Seamless fusion of vector + graph + field search * - Triple Intelligence: Seamless fusion of vector + graph + field search
* - Db: Immutable, generation-pinned database values (now/transact/asOf/with)
* - Plugins: Extensible plugin system (cortex, storage adapters) * - Plugins: Extensible plugin system (cortex, storage adapters)
* - Neural API: AI-powered clustering and analysis * - Neural API: AI-powered clustering and analysis
*/ */
// Export main Brainy class - the modern, clean API for Brainy 3.0 // Export main Brainy class
import { Brainy } from './brainy.js' import { Brainy } from './brainy.js'
export { Brainy } export { Brainy }
@ -28,6 +29,14 @@ export type {
RelateParams, RelateParams,
UpdateRelationParams, UpdateRelationParams,
FindParams, FindParams,
SimilarParams,
GetOptions,
RelatedParams,
AddManyParams,
UpdateManyParams,
RemoveManyParams,
RelateManyParams,
BatchResult,
SubtypeRegistry, SubtypeRegistry,
FillSubtypeRule, FillSubtypeRule,
FillSubtypeRules, FillSubtypeRules,
@ -152,6 +161,10 @@ export type { Migration, MigrationState, MigrationPreview, MigrationResult, Migr
// Export optimistic-concurrency types (7.31.0) // Export optimistic-concurrency types (7.31.0)
export { RevisionConflictError } from './transaction/RevisionConflictError.js' export { RevisionConflictError } from './transaction/RevisionConflictError.js'
// Named not-found errors — thrown by update/relate/updateRelation/similar/
// transact/with() when a referenced entity or relation does not exist.
export { EntityNotFoundError, RelationNotFoundError } from './errors/notFound.js'
// ============= 8.0 Db API — generational MVCC ============= // ============= 8.0 Db API — generational MVCC =============
// Immutable database values: brain.now() / brain.transact() / brain.asOf() / // Immutable database values: brain.now() / brain.transact() / brain.asOf() /
// db.with() / db.persist() / Brainy.load(). See src/db/ for the record layer. // db.with() / db.persist() / Brainy.load(). See src/db/ for the record layer.

View file

@ -59,9 +59,15 @@ export class ColumnManifest {
async load(storage: StorageAdapter): Promise<void> { async load(storage: StorageAdapter): Promise<void> {
try { try {
const path = this.manifestPath() const path = this.manifestPath()
const data = await (storage as any).readObjectFromPath(path) // readObjectFromPath is a BaseStorage primitive (protected on the
// class, not part of the StorageAdapter interface) — the column store
// owns everything under its base path, and the field-name check below
// validates the manifest before it is adopted.
const data = await (storage as unknown as {
readObjectFromPath: (path: string) => Promise<ManifestData | null>
}).readObjectFromPath(path)
if (data && data.field === this.fieldName) { if (data && data.field === this.fieldName) {
this.data = data as ManifestData this.data = data
} }
} catch { } catch {
// Not found — start fresh (data is already initialized in constructor) // Not found — start fresh (data is already initialized in constructor)
@ -76,7 +82,12 @@ export class ColumnManifest {
async save(storage: StorageAdapter): Promise<void> { async save(storage: StorageAdapter): Promise<void> {
this.data.version++ this.data.version++
const path = this.manifestPath() const path = this.manifestPath()
await (storage as any).writeObjectToPath(path, this.data) // writeObjectToPath is a BaseStorage primitive (protected on the class,
// not part of the StorageAdapter interface) — same typed boundary as
// load() above.
await (storage as unknown as {
writeObjectToPath: (path: string, data: ManifestData) => Promise<void>
}).writeObjectToPath(path, this.data)
} }
/** /**

Some files were not shown because too many files have changed in this diff Show more