feat: add neural extraction APIs with NounType taxonomy

Add brain.extract() and brain.extractConcepts() methods that use
NeuralEntityExtractor with embeddings and sophisticated NounType
taxonomy (30+ entity types) for semantic entity and concept extraction.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
David Snelling 2025-09-29 13:51:47 -07:00
parent 27cc699555
commit dd50d89ad6
41 changed files with 3807 additions and 7391 deletions

View file

@ -1,286 +0,0 @@
/**
* Knowledge Layer Augmentation for VFS
*
* Adds intelligent features to VFS without modifying core functionality:
* - Event recording for all operations
* - Semantic versioning based on content changes
* - Entity and concept extraction
* - Git bridge for import/export
*
* This is a TRUE augmentation - VFS works perfectly without it
*/
import { Brainy } from '../brainy.js'
import { BaseAugmentation } from './brainyAugmentation.js'
import { EventRecorder } from '../vfs/EventRecorder.js'
import { SemanticVersioning } from '../vfs/SemanticVersioning.js'
import { PersistentEntitySystem } from '../vfs/PersistentEntitySystem.js'
import { ConceptSystem } from '../vfs/ConceptSystem.js'
import { GitBridge } from '../vfs/GitBridge.js'
export class KnowledgeAugmentation extends BaseAugmentation {
name = 'knowledge'
timing: 'after' = 'after' // Process after VFS operations
metadata: 'none' = 'none' // No metadata access needed
operations = [] as any // VFS-specific augmentation, no operation interception
priority = 100 // Run last
constructor(config: any = {}) {
super(config)
}
async execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T> {
// Pass through - this augmentation works at VFS level, not operation level
return await next()
}
private eventRecorder?: EventRecorder
private semanticVersioning?: SemanticVersioning
private entitySystem?: PersistentEntitySystem
private conceptSystem?: ConceptSystem
private gitBridge?: GitBridge
private originalMethods: Map<string, Function> = new Map()
async initialize(context: any): Promise<void> {
await this.augment(context.brain)
}
async augment(brain: Brainy): Promise<void> {
// Only augment if VFS exists
const vfs = brain.vfs?.()
if (!vfs) {
console.warn('KnowledgeAugmentation: VFS not found, skipping')
return
}
// Initialize Knowledge Layer components
this.eventRecorder = new EventRecorder(brain)
this.semanticVersioning = new SemanticVersioning(brain)
this.entitySystem = new PersistentEntitySystem(brain)
this.conceptSystem = new ConceptSystem(brain)
this.gitBridge = new GitBridge(vfs, brain)
// Wrap VFS methods to add intelligence WITHOUT slowing them down
this.wrapMethod(vfs, 'writeFile', async (original: Function, path: string, data: Buffer, options?: any) => {
// Call original first (stays fast)
const result = await original.call(vfs, path, data, options)
// Knowledge processing in background (non-blocking)
setImmediate(async () => {
try {
// Record event
if (this.eventRecorder) {
await this.eventRecorder.recordEvent({
type: 'write',
path,
content: data,
size: data.length,
author: options?.author || 'system'
})
}
// Check for semantic versioning
if (this.semanticVersioning) {
const existingContent = await vfs.readFile(path).catch(() => null)
const shouldVersion = existingContent && this.isSemanticChange(existingContent, data)
if (shouldVersion) {
await this.semanticVersioning.createVersion(path, data, {
message: 'Automatic semantic version'
})
}
}
// Extract concepts
if (this.conceptSystem && options?.extractConcepts !== false) {
await this.conceptSystem.extractAndLinkConcepts(path, data)
}
// Extract entities
if (this.entitySystem && options?.extractEntities !== false) {
await this.entitySystem.extractEntities(data.toString('utf8'), data)
}
} catch (error) {
// Knowledge Layer errors should not affect VFS operations
console.debug('KnowledgeLayer background processing error:', error)
}
})
return result
})
this.wrapMethod(vfs, 'unlink', async (original: Function, path: string) => {
const result = await original.call(vfs, path)
// Record deletion event
setImmediate(async () => {
if (this.eventRecorder) {
await this.eventRecorder.recordEvent({
type: 'delete',
path,
author: 'system'
})
}
})
return result
})
this.wrapMethod(vfs, 'rename', async (original: Function, oldPath: string, newPath: string) => {
const result = await original.call(vfs, oldPath, newPath)
// Record rename event
setImmediate(async () => {
if (this.eventRecorder) {
await this.eventRecorder.recordEvent({
type: 'rename',
path: oldPath,
metadata: { newPath },
author: 'system'
})
}
})
return result
})
// Add Knowledge Layer methods to VFS
this.addKnowledgeMethods(vfs)
console.log('✨ Knowledge Layer augmentation enabled')
}
/**
* Wrap a VFS method to add Knowledge Layer functionality
*/
private wrapMethod(vfs: any, methodName: string, wrapper: Function): void {
const original = vfs[methodName]
if (!original) return
// Store original for cleanup
this.originalMethods.set(methodName, original)
// Replace with wrapped version
vfs[methodName] = async (...args: any[]) => {
return await wrapper(original, ...args)
}
}
/**
* Add Knowledge Layer methods to VFS
*/
private addKnowledgeMethods(vfs: any): void {
// Event history
(vfs as any).getHistory = async (path: string, options?: any) => {
if (!this.eventRecorder) throw new Error('Knowledge Layer not initialized')
return await this.eventRecorder.getHistory(path, options)
}
(vfs as any).reconstructAtTime = async (path: string, timestamp: number) => {
if (!this.eventRecorder) throw new Error('Knowledge Layer not initialized')
return await this.eventRecorder.reconstructFileAtTime(path, timestamp)
}
// Semantic versioning
(vfs as any).getVersions = async (path: string) => {
if (!this.semanticVersioning) throw new Error('Knowledge Layer not initialized')
return await this.semanticVersioning.getVersions(path)
}
(vfs as any).restoreVersion = async (path: string, versionId: string) => {
if (!this.semanticVersioning) throw new Error('Knowledge Layer not initialized')
const version = await this.semanticVersioning.getVersion(path, versionId)
if (version) {
await vfs.writeFile(path, version)
}
}
// Entities
(vfs as any).findEntity = async (query: any) => {
if (!this.entitySystem) throw new Error('Knowledge Layer not initialized')
return await this.entitySystem.findEntity(query)
}
(vfs as any).getEntityAppearances = async (entityId: string) => {
if (!this.entitySystem) throw new Error('Knowledge Layer not initialized')
return await this.entitySystem.getEvolution(entityId)
}
// Concepts
(vfs as any).getConcepts = async (path: string) => {
if (!this.conceptSystem) throw new Error('Knowledge Layer not initialized')
const concepts = await this.conceptSystem.findConcepts({ manifestedIn: path })
return concepts
}
(vfs as any).getConceptGraph = async (options?: any) => {
if (!this.conceptSystem) throw new Error('Knowledge Layer not initialized')
return await this.conceptSystem.getConceptGraph(options)
}
// Git bridge
(vfs as any).exportToGit = async (vfsPath: string, gitPath: string) => {
if (!this.gitBridge) throw new Error('Knowledge Layer not initialized')
return await this.gitBridge.exportToGit(vfsPath, gitPath)
}
(vfs as any).importFromGit = async (gitPath: string, vfsPath: string) => {
if (!this.gitBridge) throw new Error('Knowledge Layer not initialized')
return await this.gitBridge.importFromGit(gitPath, vfsPath)
}
// Temporal coupling
(vfs as any).findTemporalCoupling = async (path: string, windowMs?: number) => {
if (!this.eventRecorder) throw new Error('Knowledge Layer not initialized')
return await this.eventRecorder.findTemporalCoupling(path, windowMs)
}
}
private isSemanticChange(oldContent: Buffer, newContent: Buffer): boolean {
// Simple heuristic - significant size change or different content
const oldStr = oldContent.toString('utf8')
const newStr = newContent.toString('utf8')
// Check for significant size change (>10%)
const sizeDiff = Math.abs(oldStr.length - newStr.length) / oldStr.length
if (sizeDiff > 0.1) return true
// Check for structural changes (simplified)
const oldLines = oldStr.split('\n').filter(l => l.trim())
const newLines = newStr.split('\n').filter(l => l.trim())
// Different number of non-empty lines
return Math.abs(oldLines.length - newLines.length) > 5
}
async cleanup(brain: Brainy): Promise<void> {
const vfs = brain.vfs?.()
if (!vfs) return
// Restore original methods
for (const [methodName, original] of this.originalMethods) {
(vfs as any)[methodName] = original
}
// Remove added methods
delete (vfs as any).getHistory
delete (vfs as any).reconstructAtTime
delete (vfs as any).getVersions
delete (vfs as any).restoreVersion
delete (vfs as any).findEntity
delete (vfs as any).getEntityAppearances
delete (vfs as any).getConcepts
delete (vfs as any).getConceptGraph
delete (vfs as any).exportToGit
delete (vfs as any).importFromGit
delete (vfs as any).findTemporalCoupling
// Clean up components
this.eventRecorder = undefined
this.semanticVersioning = undefined
this.entitySystem = undefined
this.conceptSystem = undefined
this.gitBridge = undefined
console.log('Knowledge Layer augmentation removed')
}
}

View file

@ -14,7 +14,6 @@ import { CacheAugmentation } from './cacheAugmentation.js'
import { MetricsAugmentation } from './metricsAugmentation.js'
import { MonitoringAugmentation } from './monitoringAugmentation.js'
import { UniversalDisplayAugmentation } from './universalDisplayAugmentation.js'
import { KnowledgeAugmentation } from './KnowledgeAugmentation.js'
/**
* Create default augmentations for zero-config operation
@ -29,7 +28,6 @@ export function createDefaultAugmentations(
metrics?: boolean | Record<string, any>
monitoring?: boolean | Record<string, any>
display?: boolean | Record<string, any>
knowledge?: boolean | Record<string, any>
} = {}
): BaseAugmentation[] {
const augmentations: BaseAugmentation[] = []
@ -56,19 +54,14 @@ export function createDefaultAugmentations(
// Monitoring augmentation (was HealthMonitor)
// Only enable by default in distributed mode
const isDistributed = process.env.BRAINY_MODE === 'distributed' ||
const isDistributed = process.env.BRAINY_MODE === 'distributed' ||
process.env.BRAINY_DISTRIBUTED === 'true'
if (config.monitoring !== false && (config.monitoring || isDistributed)) {
const monitoringConfig = typeof config.monitoring === 'object' ? config.monitoring : {}
augmentations.push(new MonitoringAugmentation(monitoringConfig))
}
// Knowledge Layer augmentation for VFS intelligence
if (config.knowledge !== false) {
augmentations.push(new KnowledgeAugmentation() as any)
}
return augmentations
}

View file

@ -19,6 +19,7 @@ import { AugmentationRegistry, AugmentationContext } from './augmentations/brain
import { createDefaultAugmentations } from './augmentations/defaultAugmentations.js'
import { ImprovedNeuralAPI } from './neural/improvedNeuralAPI.js'
import { NaturalLanguageProcessor } from './neural/naturalLanguageProcessor.js'
import { NeuralEntityExtractor, ExtractedEntity } from './neural/entityExtractor.js'
import { TripleIntelligenceSystem } from './triple/TripleIntelligenceSystem.js'
import { VirtualFileSystem } from './vfs/VirtualFileSystem.js'
import { MetadataIndexManager } from './utils/metadataIndex.js'
@ -84,6 +85,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
// Sub-APIs (lazy-loaded)
private _neural?: ImprovedNeuralAPI
private _nlp?: NaturalLanguageProcessor
private _extractor?: NeuralEntityExtractor
private _tripleIntelligence?: TripleIntelligenceSystem
private _vfs?: VirtualFileSystem
@ -1580,6 +1582,76 @@ export class Brainy<T = any> implements BrainyInterface<T> {
return this._nlp
}
/**
* Entity Extraction API - Neural extraction with NounType taxonomy
*
* Extracts entities from text using:
* - Pattern-based candidate detection
* - Embedding-based type classification
* - Context-aware confidence scoring
*
* @param text - Text to extract entities from
* @param options - Extraction options
* @returns Array of extracted entities with types and confidence
*
* @example
* const entities = await brain.extract('John Smith founded Acme Corp in New York')
* // [
* // { text: 'John Smith', type: NounType.Person, confidence: 0.95 },
* // { text: 'Acme Corp', type: NounType.Organization, confidence: 0.92 },
* // { text: 'New York', type: NounType.Location, confidence: 0.88 }
* // ]
*/
async extract(
text: string,
options?: {
types?: NounType[]
confidence?: number
includeVectors?: boolean
neuralMatching?: boolean
}
): Promise<ExtractedEntity[]> {
if (!this._extractor) {
this._extractor = new NeuralEntityExtractor(this)
}
return await this._extractor.extract(text, options)
}
/**
* Extract concepts from text
*
* Simplified interface for concept/topic extraction
* Returns only concept names as strings for easy metadata population
*
* @param text - Text to extract concepts from
* @param options - Extraction options
* @returns Array of concept names
*
* @example
* const concepts = await brain.extractConcepts('Using OAuth for authentication')
* // ['oauth', 'authentication']
*/
async extractConcepts(
text: string,
options?: {
confidence?: number
limit?: number
}
): Promise<string[]> {
const entities = await this.extract(text, {
types: [NounType.Concept, NounType.Topic],
confidence: options?.confidence || 0.7,
neuralMatching: true
})
// Deduplicate and normalize
const conceptSet = new Set(entities.map(e => e.text.toLowerCase()))
const concepts = Array.from(conceptSet)
// Apply limit if specified
return options?.limit ? concepts.slice(0, options.limit) : concepts
}
/**
* Virtual File System API - Knowledge Operating System
*/

View file

@ -1,778 +0,0 @@
/**
* Universal Concept System for VFS
*
* Manages concepts that transcend files and exist independently
* Ideas that can be linked to multiple manifestations across domains
* PRODUCTION-READY: Real implementation using Brainy
*/
import { Brainy } from '../brainy.js'
import { NounType, VerbType } from '../types/graphTypes.js'
import { cosineDistance } from '../utils/distance.js'
import { v4 as uuidv4 } from '../universal/uuid.js'
import { EntityManager, ManagedEntity } from './EntityManager.js'
/**
* Universal concept that exists independently of files
*/
export interface UniversalConcept extends ManagedEntity {
id: string
name: string
description?: string
domain: string // 'business', 'technical', 'creative', 'academic', etc.
category: string // 'pattern', 'principle', 'method', 'entity', etc.
keywords: string[] // Associated keywords/tags
links: ConceptLink[] // Links to other concepts
manifestations: ConceptManifestation[] // Where concept appears
strength: number // How well-established this concept is (0-1)
created: number
lastUpdated: number
version: number
metadata: Record<string, any>
conceptType?: string // For EntityManager queries
}
/**
* A link between concepts
*/
export interface ConceptLink {
id: string
targetConceptId: string
relationship: 'extends' | 'implements' | 'uses' | 'opposite' | 'related' | 'contains' | 'part-of'
strength: number // How strong the relationship is (0-1)
context?: string // Why/how they're related
bidirectional: boolean // Is the relationship mutual?
}
/**
* A manifestation of a concept in a specific location
*/
export interface ConceptManifestation extends ManagedEntity {
id: string
conceptId: string
filePath: string
context: string // Surrounding content
form: 'definition' | 'usage' | 'example' | 'discussion' | 'implementation'
position?: {
line?: number
column?: number
offset?: number
}
confidence: number // How confident we are this represents the concept (0-1)
timestamp: number
extractedBy: 'manual' | 'auto' | 'ai'
}
/**
* Configuration for concept system
*/
export interface ConceptSystemConfig {
autoLink?: boolean // Auto-link related concepts
similarityThreshold?: number // For concept matching (0-1)
maxManifestations?: number // Max manifestations to track per concept
strengthDecay?: number // How concept strength decays over time
}
/**
* Concept graph structure for visualization
*/
export interface ConceptGraph {
concepts: Array<{
id: string
name: string
domain: string
strength: number
manifestationCount: number
}>
links: Array<{
source: string
target: string
relationship: string
strength: number
}>
}
/**
* Universal Concept System
*
* Manages concepts that exist independently of any specific file or context
* Examples:
* - "Authentication" concept appearing in docs, code, tests
* - "Customer Journey" concept in marketing, UX, analytics
* - "Dependency Injection" pattern across multiple codebases
* - "Sustainability" theme in various research papers
*/
export class ConceptSystem extends EntityManager {
private config: Required<ConceptSystemConfig>
private conceptCache = new Map<string, UniversalConcept>()
constructor(
brain: Brainy,
config?: ConceptSystemConfig
) {
super(brain, 'vfs-concept')
this.config = {
autoLink: config?.autoLink ?? false,
similarityThreshold: config?.similarityThreshold ?? 0.7,
maxManifestations: config?.maxManifestations ?? 1000,
strengthDecay: config?.strengthDecay ?? 0.95 // 5% decay over time
}
}
/**
* Create a new universal concept
*/
async createConcept(concept: Omit<UniversalConcept, 'id' | 'created' | 'lastUpdated' | 'version' | 'links' | 'manifestations'>): Promise<string> {
const conceptId = uuidv4()
const timestamp = Date.now()
const universalConcept: UniversalConcept = {
id: conceptId,
name: concept.name,
description: concept.description,
domain: concept.domain,
category: concept.category,
keywords: concept.keywords,
strength: concept.strength,
metadata: concept.metadata || {},
created: timestamp,
lastUpdated: timestamp,
version: 1,
links: [],
manifestations: [],
conceptType: 'universal' // Add conceptType for querying
}
// Generate embedding for concept
let embedding: number[] | undefined
try {
embedding = await this.generateConceptEmbedding(universalConcept)
} catch (error) {
console.warn('Failed to generate concept embedding:', error)
}
// Store concept using EntityManager
await this.storeEntity(
universalConcept,
NounType.Concept,
embedding,
Buffer.from(JSON.stringify(universalConcept))
)
// Auto-link to similar concepts if enabled
if (this.config.autoLink) {
await this.autoLinkConcept(conceptId)
}
// Update cache
this.conceptCache.set(conceptId, universalConcept)
return conceptId // Return domain ID, not Brainy ID
}
/**
* Find concepts by various criteria
*/
async findConcepts(query: {
name?: string
domain?: string
category?: string
keywords?: string[]
similar?: string // Find concepts similar to this text
manifestedIn?: string // Find concepts that appear in this file
}): Promise<UniversalConcept[]> {
const searchQuery: any = {
conceptType: 'universal',
system: 'vfs-concept'
}
// Direct attribute matching
if (query.name) searchQuery.name = query.name
if (query.domain) searchQuery.domain = query.domain
if (query.category) searchQuery.category = query.category
// Keyword matching
if (query.keywords && query.keywords.length > 0) {
searchQuery.keywords = { $in: query.keywords }
}
// File manifestation search
if (query.manifestedIn) {
// Find concepts that have manifestations in this file
const manifestationResults = await this.findEntities<ConceptManifestation>(
{
filePath: query.manifestedIn,
eventType: 'concept-manifestation'
},
NounType.Event,
1000
)
const conceptIds = manifestationResults.map(m => m.conceptId)
if (conceptIds.length > 0) {
searchQuery.id = { $in: conceptIds }
} else {
return [] // No concepts found in this file
}
}
// Search using EntityManager
const results = await this.findEntities<UniversalConcept>(
searchQuery,
NounType.Concept,
1000
)
// If searching for similar concepts, use vector similarity
if (query.similar) {
try {
const queryEmbedding = await this.generateTextEmbedding(query.similar)
if (queryEmbedding) {
// Get all concepts and rank by similarity
const allConcepts = await this.findEntities<UniversalConcept>(
{ conceptType: 'universal' },
NounType.Concept,
10000
)
// For similarity search, we need to get the actual vector data from Brainy
const conceptsWithVectors = []
for (const concept of allConcepts) {
if (concept.brainyId) {
const brainyEntity = await this.brain.get(concept.brainyId)
if (brainyEntity?.vector && brainyEntity.vector.length > 0) {
conceptsWithVectors.push({
concept,
vector: brainyEntity.vector
})
}
}
}
const withSimilarity = conceptsWithVectors
.map(c => ({
concept: c.concept,
similarity: 1 - cosineDistance(queryEmbedding, c.vector)
}))
.filter(s => s.similarity > this.config.similarityThreshold)
.sort((a, b) => b.similarity - a.similarity)
return withSimilarity.map(s => s.concept)
}
} catch (error) {
console.warn('Failed to perform concept similarity search:', error)
}
}
return results
}
/**
* Link two concepts together
*/
async linkConcept(
fromConceptId: string,
toConceptId: string,
relationship: ConceptLink['relationship'],
options?: {
strength?: number
context?: string
bidirectional?: boolean
}
): Promise<string> {
const linkId = uuidv4()
const fromConcept = await this.getConcept(fromConceptId)
const toConcept = await this.getConcept(toConceptId)
if (!fromConcept || !toConcept) {
throw new Error('One or both concepts not found')
}
// Create link
const link: ConceptLink = {
id: linkId,
targetConceptId: toConceptId,
relationship,
strength: options?.strength ?? 0.8,
context: options?.context,
bidirectional: options?.bidirectional ?? false
}
// Add link to source concept
fromConcept.links.push(link)
fromConcept.lastUpdated = Date.now()
await this.updateConcept(fromConcept)
// Add bidirectional link if specified
if (link.bidirectional) {
const reverseRelationship = this.getReverseRelationship(relationship)
const reverseLink: ConceptLink = {
id: uuidv4(),
targetConceptId: fromConceptId,
relationship: reverseRelationship,
strength: link.strength,
context: link.context,
bidirectional: true
}
toConcept.links.push(reverseLink)
toConcept.lastUpdated = Date.now()
await this.updateConcept(toConcept)
}
// Create relationship using EntityManager
await this.createRelationship(
fromConceptId,
toConceptId,
this.getVerbType(relationship),
{
strength: link.strength,
context: link.context,
bidirectional: link.bidirectional
}
)
return linkId
}
/**
* Record a manifestation of a concept in a file
*/
async recordManifestation(
conceptId: string,
filePath: string,
context: string,
form: ConceptManifestation['form'],
options?: {
position?: ConceptManifestation['position']
confidence?: number
extractedBy?: ConceptManifestation['extractedBy']
}
): Promise<string> {
const concept = await this.getConcept(conceptId)
if (!concept) {
throw new Error(`Concept ${conceptId} not found`)
}
const manifestationId = uuidv4()
const timestamp = Date.now()
const manifestation: ConceptManifestation = {
id: manifestationId,
conceptId,
filePath,
context,
form,
position: options?.position,
confidence: options?.confidence ?? 1.0,
timestamp,
extractedBy: options?.extractedBy ?? 'manual'
}
// Store manifestation as managed entity (with eventType for manifestations)
const manifestationWithEventType = {
...manifestation,
eventType: 'concept-manifestation'
}
await this.storeEntity(
manifestationWithEventType,
NounType.Event,
undefined,
Buffer.from(context)
)
// Create relationship to concept using EntityManager
await this.createRelationship(
manifestationId,
conceptId,
VerbType.Implements
)
// Update concept with new manifestation
concept.manifestations.push(manifestation)
concept.lastUpdated = timestamp
// Update concept strength based on manifestations
concept.strength = Math.min(1.0, concept.strength + 0.1)
// Prune old manifestations if needed
if (concept.manifestations.length > this.config.maxManifestations) {
concept.manifestations = concept.manifestations
.sort((a, b) => b.timestamp - a.timestamp)
.slice(0, this.config.maxManifestations)
}
// Update stored concept
await this.updateConcept(concept)
return manifestationId
}
/**
* Extract and link concepts from content
*/
async extractAndLinkConcepts(filePath: string, content: Buffer): Promise<string[]> {
if (!this.config.autoLink) {
return []
}
const text = content.toString('utf8')
const extractedConcepts: string[] = []
// Simple concept extraction patterns
// In production, this would use advanced NLP/AI models
const conceptPatterns = [
// Technical concepts
/\b(authentication|authorization|validation|encryption|caching|logging|monitoring)\b/gi,
// Business concepts
/\b(customer\s+journey|user\s+experience|business\s+logic|revenue\s+model)\b/gi,
// Design patterns
/\b(singleton|factory|observer|strategy|adapter|decorator)\b/gi,
// General concepts
/\b(security|performance|scalability|maintainability|reliability)\b/gi
]
for (const pattern of conceptPatterns) {
const matches = text.matchAll(pattern)
for (const match of matches) {
const conceptName = match[0].toLowerCase()
const context = this.extractContext(text, match.index || 0)
// Find or create concept
let concepts = await this.findConcepts({ name: conceptName })
let conceptId: string
if (concepts.length === 0) {
// Create new concept
conceptId = await this.createConcept({
name: conceptName,
domain: this.detectDomain(conceptName, text),
category: this.detectCategory(conceptName),
keywords: [conceptName],
strength: 0.5,
metadata: {}
})
extractedConcepts.push(conceptId)
} else {
conceptId = concepts[0].id
}
// Record manifestation
await this.recordManifestation(
conceptId,
filePath,
context,
this.detectManifestationForm(context),
{
confidence: 0.8,
extractedBy: 'auto'
}
)
}
}
return extractedConcepts
}
/**
* Get concept graph for visualization
*/
async getConceptGraph(options?: {
domain?: string
minStrength?: number
maxConcepts?: number
}): Promise<ConceptGraph> {
const query: any = {
conceptType: 'universal',
system: 'vfs-concept'
}
if (options?.domain) {
query.domain = options.domain
}
if (options?.minStrength) {
query.strength = { $gte: options.minStrength }
}
const concepts = await this.findEntities<UniversalConcept>(
query,
NounType.Concept,
options?.maxConcepts || 1000
)
// Build graph structure
const graphConcepts = concepts.map(c => ({
id: c.id,
name: c.name,
domain: c.domain,
strength: c.strength,
manifestationCount: c.manifestations.length
}))
const graphLinks: ConceptGraph['links'] = []
for (const concept of concepts) {
for (const link of concept.links) {
// Only include links to concepts in our result set
if (concepts.find(c => c.id === link.targetConceptId)) {
graphLinks.push({
source: concept.id,
target: link.targetConceptId,
relationship: link.relationship,
strength: link.strength
})
}
}
}
return {
concepts: graphConcepts,
links: graphLinks
}
}
/**
* Find appearances of a concept
*/
async findAppearances(conceptId: string, options?: {
filePath?: string
form?: ConceptManifestation['form']
minConfidence?: number
limit?: number
}): Promise<ConceptManifestation[]> {
const query: any = {
conceptId,
eventType: 'concept-manifestation',
system: 'vfs-concept'
}
if (options?.filePath) {
query.filePath = options.filePath
}
if (options?.form) {
query.form = options.form
}
if (options?.minConfidence) {
query.confidence = { $gte: options.minConfidence }
}
const manifestations = await this.findEntities<ConceptManifestation>(
query,
NounType.Event,
options?.limit || 1000
)
return manifestations.sort((a, b) => b.timestamp - a.timestamp)
}
/**
* Auto-link concept to similar concepts
*/
private async autoLinkConcept(conceptId: string): Promise<void> {
const concept = await this.getConcept(conceptId)
if (!concept) return
// Find similar concepts
const similar = await this.findConcepts({
similar: concept.name + ' ' + (concept.description || '')
})
for (const similarConcept of similar) {
if (similarConcept.id === conceptId) continue
// Calculate relationship strength based on similarity
const strength = await this.calculateConceptSimilarity(concept, similarConcept)
if (strength > this.config.similarityThreshold) {
await this.linkConcept(
conceptId,
similarConcept.id,
'related',
{ strength, bidirectional: true }
)
}
}
}
/**
* Get concept by ID
*/
private async getConcept(conceptId: string): Promise<UniversalConcept | null> {
// Check cache first
if (this.conceptCache.has(conceptId)) {
return this.conceptCache.get(conceptId)!
}
// Query using EntityManager
const concept = await this.getEntity<UniversalConcept>(conceptId)
if (concept) {
this.conceptCache.set(conceptId, concept)
}
return concept
}
/**
* Update stored concept
*/
private async updateConcept(concept: UniversalConcept): Promise<void> {
// Add conceptType metadata before updating
concept.conceptType = 'universal'
// Update using EntityManager
await this.updateEntity(concept)
// Update cache
this.conceptCache.set(concept.id, concept)
}
/**
* Calculate similarity between two concepts
*/
private async calculateConceptSimilarity(
concept1: UniversalConcept,
concept2: UniversalConcept
): Promise<number> {
// Simple similarity calculation
let similarity = 0
// Domain similarity
if (concept1.domain === concept2.domain) similarity += 0.3
// Category similarity
if (concept1.category === concept2.category) similarity += 0.2
// Keyword overlap
const commonKeywords = concept1.keywords.filter(k => concept2.keywords.includes(k))
similarity += (commonKeywords.length / Math.max(concept1.keywords.length, concept2.keywords.length)) * 0.3
// Name similarity (simple string comparison)
const nameWords1 = concept1.name.toLowerCase().split(/\s+/)
const nameWords2 = concept2.name.toLowerCase().split(/\s+/)
const commonWords = nameWords1.filter(w => nameWords2.includes(w))
similarity += (commonWords.length / Math.max(nameWords1.length, nameWords2.length)) * 0.2
return Math.min(1.0, similarity)
}
/**
* Generate embedding for concept
*/
private async generateConceptEmbedding(concept: UniversalConcept): Promise<number[] | undefined> {
try {
const text = [
concept.name,
concept.description || '',
concept.domain,
concept.category,
...(Array.isArray(concept.keywords) ? concept.keywords : [])
].join(' ')
return await this.generateTextEmbedding(text)
} catch (error) {
console.error('Failed to generate concept embedding:', error)
return undefined
}
}
/**
* Generate embedding for text
*/
private async generateTextEmbedding(text: string): Promise<number[] | undefined> {
try {
// Generate embedding using Brainy's embed method
const vector = await this.brain.embed(text)
return vector
} catch (error) {
console.debug('Failed to generate embedding:', error)
return undefined
}
}
/**
* Get reverse relationship type
*/
private getReverseRelationship(relationship: ConceptLink['relationship']): ConceptLink['relationship'] {
const reverseMap: Record<ConceptLink['relationship'], ConceptLink['relationship']> = {
'extends': 'extended-by' as any,
'implements': 'implemented-by' as any,
'uses': 'used-by' as any,
'opposite': 'opposite',
'related': 'related',
'contains': 'part-of',
'part-of': 'contains'
}
return reverseMap[relationship] || 'related'
}
/**
* Map concept relationship to VerbType
*/
private getVerbType(relationship: ConceptLink['relationship']): VerbType {
const verbMap: Record<ConceptLink['relationship'], VerbType> = {
'extends': VerbType.Extends,
'implements': VerbType.Implements,
'uses': VerbType.Uses,
'opposite': VerbType.Conflicts,
'related': VerbType.RelatedTo,
'contains': VerbType.Contains,
'part-of': VerbType.PartOf
}
return verbMap[relationship] || VerbType.RelatedTo
}
/**
* Detect concept domain from context
*/
private detectDomain(conceptName: string, context: string): string {
if (/import|export|function|class|const|var|let/.test(context)) return 'technical'
if (/customer|user|business|revenue|market/.test(context)) return 'business'
if (/design|pattern|architecture/.test(context)) return 'design'
if (/research|study|analysis/.test(context)) return 'academic'
return 'general'
}
/**
* Detect concept category
*/
private detectCategory(conceptName: string): string {
if (/pattern|strategy|factory|singleton/.test(conceptName)) return 'pattern'
if (/principle|rule|law/.test(conceptName)) return 'principle'
if (/method|approach|technique/.test(conceptName)) return 'method'
if (/entity|object|model/.test(conceptName)) return 'entity'
return 'concept'
}
/**
* Detect manifestation form from context
*/
private detectManifestationForm(context: string): ConceptManifestation['form'] {
if (context.includes('definition') || context.includes('is defined as')) return 'definition'
if (context.includes('example') || context.includes('for instance')) return 'example'
if (context.includes('implements') || context.includes('function')) return 'implementation'
if (context.includes('discussed') || context.includes('explains')) return 'discussion'
return 'usage'
}
/**
* Extract context around a position
*/
private extractContext(text: string, position: number, radius = 150): string {
const start = Math.max(0, position - radius)
const end = Math.min(text.length, position + radius)
return text.slice(start, end)
}
/**
* Clear concept cache
*/
clearCache(conceptId?: string): void {
if (conceptId) {
this.conceptCache.delete(conceptId)
} else {
this.conceptCache.clear()
}
}
}

View file

@ -1,291 +0,0 @@
/**
* EntityManager Base Class
*
* Provides standardized entity ID management for all Knowledge Layer components
* Solves the root cause of ID mismatch issues by establishing clear patterns
*/
import { Brainy } from '../brainy.js'
import { NounType } from '../types/graphTypes.js'
import { v4 as uuidv4 } from '../universal/uuid.js'
/**
* Standard entity structure used by all Knowledge Layer components
*/
export interface ManagedEntity {
/** Domain-specific ID (for external references) */
id: string
/** The actual Brainy entity ID (for internal operations) */
brainyId?: string
/** Entity metadata */
[key: string]: any
}
/**
* ID mapping to track domain IDs to Brainy entity IDs
*/
interface EntityIdMapping {
domainId: string
brainyId: string
system: string
type: string
}
/**
* EntityManager Base Class
*
* All Knowledge Layer components should extend this to get standardized:
* - Entity storage and retrieval
* - ID management and mapping
* - Query patterns
* - Relationship creation
*/
export abstract class EntityManager {
private idMappings = new Map<string, string>() // domainId -> brainyId
private brainyToMappings = new Map<string, string>() // brainyId -> domainId
constructor(
protected brain: Brainy,
protected systemName: string
) {}
/**
* Store an entity with proper ID management
*/
protected async storeEntity<T extends ManagedEntity>(
entity: T,
nounType: NounType,
embedding?: number[],
data?: any
): Promise<string> {
// Generate domain ID if not provided
if (!entity.id) {
entity.id = uuidv4()
}
// Store in Brainy and get the Brainy entity ID
const brainyId = await this.brain.add({
type: nounType,
data: data || Buffer.from(JSON.stringify(entity)),
metadata: {
...entity,
system: this.systemName,
domainId: entity.id, // Store the domain ID for reference
storedAt: Date.now()
},
vector: embedding
})
// Store ID mapping
this.idMappings.set(entity.id, brainyId)
this.brainyToMappings.set(brainyId, entity.id)
// Update entity with Brainy ID
entity.brainyId = brainyId
return entity.id // Return domain ID for external use
}
/**
* Update an existing entity
*/
protected async updateEntity<T extends ManagedEntity>(
entity: T,
embedding?: number[]
): Promise<void> {
const brainyId = this.idMappings.get(entity.id)
if (!brainyId) {
throw new Error(`Entity ${entity.id} not found in mappings`)
}
await this.brain.update({
id: brainyId,
data: Buffer.from(JSON.stringify(entity)),
metadata: {
...entity,
system: this.systemName,
domainId: entity.id,
updatedAt: Date.now()
},
vector: embedding
})
}
/**
* Retrieve entity by domain ID
*/
protected async getEntity<T extends ManagedEntity>(domainId: string): Promise<T | null> {
// First try from cache
let brainyId = this.idMappings.get(domainId)
// If not in cache, search by domain ID in metadata
if (!brainyId) {
const results = await this.brain.find({
where: {
domainId,
system: this.systemName
},
limit: 1
})
if (results.length === 0) {
return null
}
brainyId = results[0].entity.id
// Cache the mapping
this.idMappings.set(domainId, brainyId)
this.brainyToMappings.set(brainyId, domainId)
}
// Get entity using Brainy ID
const brainyEntity = await this.brain.get(brainyId)
if (!brainyEntity) {
return null
}
// Parse entity from metadata
const entity = brainyEntity.metadata as T
entity.brainyId = brainyId
return entity
}
/**
* Find entities by metadata criteria
*/
protected async findEntities<T extends ManagedEntity>(
criteria: Record<string, any>,
nounType?: NounType,
limit = 100
): Promise<T[]> {
const query: any = {
...criteria,
system: this.systemName
}
const results = await this.brain.find({
where: query,
type: nounType,
limit
})
const entities: T[] = []
for (const result of results) {
const entity = result.entity.metadata as T
entity.brainyId = result.entity.id
// Update mappings cache
this.idMappings.set(entity.id, result.entity.id)
this.brainyToMappings.set(result.entity.id, entity.id)
entities.push(entity)
}
return entities
}
/**
* Create relationship between entities using proper Brainy IDs
*/
protected async createRelationship(
fromDomainId: string,
toDomainId: string,
relationshipType: any,
metadata?: Record<string, any>
): Promise<void> {
// Get Brainy IDs for both entities
const fromBrainyId = await this.getBrainyId(fromDomainId)
const toBrainyId = await this.getBrainyId(toDomainId)
if (!fromBrainyId || !toBrainyId) {
throw new Error(`Cannot find Brainy IDs for relationship: ${fromDomainId} -> ${toDomainId}`)
}
// Create relationship using Brainy IDs
await this.brain.relate({
from: fromBrainyId,
to: toBrainyId,
type: relationshipType,
metadata
})
}
/**
* Get Brainy ID for a domain ID
*/
protected async getBrainyId(domainId: string): Promise<string | null> {
// Check cache first
let brainyId = this.idMappings.get(domainId)
if (!brainyId) {
// Search in Brainy
const results = await this.brain.find({
where: {
domainId,
system: this.systemName
},
limit: 1
})
if (results.length > 0) {
brainyId = results[0].entity.id
// Cache the mapping
this.idMappings.set(domainId, brainyId)
this.brainyToMappings.set(brainyId, domainId)
}
}
return brainyId || null
}
/**
* Get domain ID for a Brainy ID
*/
protected getDomainId(brainyId: string): string | null {
return this.brainyToMappings.get(brainyId) || null
}
/**
* Delete entity by domain ID
*/
protected async deleteEntity(domainId: string): Promise<void> {
const brainyId = await this.getBrainyId(domainId)
if (brainyId) {
await this.brain.delete(brainyId)
// Clean up mappings
this.idMappings.delete(domainId)
this.brainyToMappings.delete(brainyId)
}
}
/**
* Clear ID mapping cache (useful for tests)
*/
protected clearMappingCache(): void {
this.idMappings.clear()
this.brainyToMappings.clear()
}
/**
* Batch load mappings for performance
*/
protected async loadMappings(domainIds: string[]): Promise<void> {
const unknownIds = domainIds.filter(id => !this.idMappings.has(id))
if (unknownIds.length === 0) return
const results = await this.brain.find({
where: {
domainId: { $in: unknownIds },
system: this.systemName
},
limit: unknownIds.length
})
for (const result of results) {
const domainId = result.entity.metadata.domainId
this.idMappings.set(domainId, result.entity.id)
this.brainyToMappings.set(result.entity.id, domainId)
}
}
}

View file

@ -1,354 +0,0 @@
/**
* Event Recording System for VFS
*
* Records every file operation as an event for complete history tracking
* PRODUCTION-READY: No mocks, real implementation
*/
import { Brainy } from '../brainy.js'
import { NounType, VerbType } from '../types/graphTypes.js'
import { v4 as uuidv4 } from '../universal/uuid.js'
import { createHash } from 'crypto'
import { EntityManager, ManagedEntity } from './EntityManager.js'
/**
* File operation event
*/
export interface FileEvent extends ManagedEntity {
id: string
type: 'create' | 'write' | 'append' | 'delete' | 'move' | 'rename' | 'mkdir' | 'rmdir'
path: string
timestamp: number
content?: Buffer
size?: number
hash?: string
author?: string
metadata?: Record<string, any>
previousHash?: string // For tracking changes
oldPath?: string // For move/rename operations
eventType?: string // For EntityManager queries
}
/**
* Event Recorder - Stores all file operations as searchable events
*/
export class EventRecorder extends EntityManager {
constructor(brain: Brainy) {
super(brain, 'vfs')
}
/**
* Record a file operation event
*/
async recordEvent(event: Omit<FileEvent, 'id' | 'timestamp'>): Promise<string> {
const eventId = uuidv4()
const timestamp = Date.now()
// Calculate content hash if content provided
const hash = event.content
? createHash('sha256').update(event.content).digest('hex')
: undefined
// Create file event
const fileEvent: FileEvent = {
id: eventId,
type: event.type,
path: event.path,
timestamp,
content: event.content,
size: event.size,
hash,
author: event.author,
metadata: event.metadata,
previousHash: event.previousHash,
oldPath: event.oldPath
}
// Generate embedding for content-based events
const embedding = event.content && event.content.length < 100000
? await this.generateEventEmbedding(event)
: undefined
// Add eventType for event classification
const eventWithType = {
...fileEvent,
eventType: 'file-operation'
}
// Store event using EntityManager
await this.storeEntity(
eventWithType,
NounType.Event,
embedding,
event.content || Buffer.from(JSON.stringify(event))
)
return eventId
}
/**
* Get all events matching criteria
*/
async getEvents(options?: {
since?: number
until?: number
types?: string[]
limit?: number
}): Promise<FileEvent[]> {
const limit = options?.limit || 100
const since = options?.since || 0
const until = options?.until || Date.now()
const query: any = {
eventType: 'file-operation'
}
// Add time filters
if (since || until) {
query.timestamp = {}
if (since) query.timestamp.$gte = since
if (until) query.timestamp.$lte = until
}
// Add type filter
if (options?.types && options.types.length > 0) {
query.type = { $in: options.types }
}
// Query using EntityManager
const events = await this.findEntities<FileEvent>(query, NounType.Event, limit)
return events.sort((a, b) => b.timestamp - a.timestamp)
}
/**
* Get complete history for a file
*/
async getHistory(path: string, options?: {
limit?: number
since?: number
until?: number
types?: FileEvent['type'][]
}): Promise<FileEvent[]> {
const query: any = {
path,
eventType: 'file-operation'
}
// Add time filters
if (options?.since || options?.until) {
query.timestamp = {}
if (options.since) query.timestamp.$gte = options.since
if (options.until) query.timestamp.$lte = options.until
}
// Add type filter
if (options?.types && options.types.length > 0) {
query.type = { $in: options.types }
}
// Query using EntityManager
const events = await this.findEntities<FileEvent>(query, NounType.Event, options?.limit || 100)
// Sort by timestamp (newest first)
return events.sort((a, b) => b.timestamp - a.timestamp)
}
/**
* Replay events to reconstruct file state at a specific time
*/
async reconstructFileAtTime(path: string, timestamp: number): Promise<Buffer | null> {
// Get all events up to the specified time
const events = await this.getHistory(path, {
until: timestamp,
types: ['create', 'write', 'append', 'delete']
})
// Sort chronologically for replay
const chronological = events.reverse()
// Find last write or create event
let lastContent: Buffer | null = null
let deleted = false
for (const event of chronological) {
switch (event.type) {
case 'create':
case 'write':
lastContent = event.content || null
deleted = false
break
case 'append':
if (lastContent && event.content) {
lastContent = Buffer.concat([lastContent, event.content])
}
break
case 'delete':
deleted = true
lastContent = null
break
}
}
return deleted ? null : lastContent
}
/**
* Get file changes between two timestamps
*/
async getChanges(since: number, until?: number): Promise<FileEvent[]> {
const query: any = {
eventType: 'file-operation',
timestamp: { $gte: since }
}
if (until) {
query.timestamp.$lte = until
}
// Query using EntityManager
const events = await this.findEntities<FileEvent>(query, NounType.Event, 1000)
return events.map(event => ({
id: event.id,
type: event.type,
path: event.path,
timestamp: event.timestamp,
size: event.size,
hash: event.hash,
author: event.author
} as FileEvent))
}
/**
* Calculate statistics for a file or directory
*/
async getStatistics(path: string): Promise<{
totalEvents: number
firstEvent: number | null
lastEvent: number | null
totalWrites: number
totalBytes: number
authors: string[]
}> {
const events = await this.getHistory(path, { limit: 10000 })
if (events.length === 0) {
return {
totalEvents: 0,
firstEvent: null,
lastEvent: null,
totalWrites: 0,
totalBytes: 0,
authors: []
}
}
const stats = {
totalEvents: events.length,
firstEvent: events[events.length - 1].timestamp, // Oldest
lastEvent: events[0].timestamp, // Newest
totalWrites: 0,
totalBytes: 0,
authors: new Set<string>()
}
for (const event of events) {
if (event.type === 'write' || event.type === 'append') {
stats.totalWrites++
stats.totalBytes += event.size || 0
}
if (event.author) {
stats.authors.add(event.author)
}
}
return {
...stats,
authors: Array.from(stats.authors)
}
}
/**
* Find files that changed together (temporal coupling)
*/
async findTemporalCoupling(path: string, windowMs = 60000): Promise<Map<string, number>> {
const events = await this.getHistory(path)
const coupling = new Map<string, number>()
for (const event of events) {
// Find other files changed within the time window
const related = await this.getChanges(
event.timestamp - windowMs,
event.timestamp + windowMs
)
for (const relatedEvent of related) {
if (relatedEvent.path !== path) {
const count = coupling.get(relatedEvent.path) || 0
coupling.set(relatedEvent.path, count + 1)
}
}
}
// Sort by coupling strength
return new Map(
Array.from(coupling.entries())
.sort((a, b) => b[1] - a[1])
)
}
/**
* Generate embedding for an event (for semantic search)
*/
private async generateEventEmbedding(event: Omit<FileEvent, 'id' | 'timestamp'>): Promise<number[] | undefined> {
try {
// Generate embedding based on event type and content
let textToEmbed: string
if (event.type === 'write' || event.type === 'create') {
// For write/create events with content, embed the content
if (event.content && event.content.length < 100000) {
// Convert Buffer to string for text files
textToEmbed = Buffer.isBuffer(event.content)
? event.content.toString('utf8', 0, Math.min(10240, event.content.length))
: String(event.content).slice(0, 10240)
} else {
// For large files or no content, create descriptive text
textToEmbed = `File ${event.type} event at ${event.path}, size: ${event.size || 0} bytes`
}
} else {
// For other events (read, delete, rename, etc), create descriptive text
textToEmbed = `File ${event.type} event at ${event.path}${event.oldPath ? ` from ${event.oldPath}` : ''}`
}
// Use Brainy's embed function
const vector = await this.brain.embed(textToEmbed)
return vector
} catch (error) {
console.error('Failed to generate event embedding:', error)
return undefined
}
}
/**
* Prune old events (for storage management)
*/
async pruneEvents(olderThan: number, keepEvery = 10): Promise<number> {
const events = await this.getChanges(0, Date.now() - olderThan)
let pruned = 0
for (let i = 0; i < events.length; i++) {
// Keep every Nth event for history sampling
if (i % keepEvery !== 0) {
await this.deleteEntity(events[i].id)
pruned++
}
}
return pruned
}
}

View file

@ -1,707 +0,0 @@
/**
* Git Bridge for VFS
*
* Provides Git import/export capabilities without Git dependencies
* Enables migration to/from Git repositories while preserving VFS intelligence
* PRODUCTION-READY: Real implementation using filesystem operations
*/
import { VirtualFileSystem } from './VirtualFileSystem.js'
import { VFSDirent } from './types.js'
import { Brainy } from '../brainy.js'
import { NounType, VerbType } from '../types/graphTypes.js'
import { v4 as uuidv4 } from '../universal/uuid.js'
import { createHash } from 'crypto'
import { promises as fs } from 'fs'
import * as path from 'path'
/**
* Git repository representation
*/
export interface GitRepository {
path: string
branches: GitBranch[]
commits: GitCommit[]
files: GitFile[]
metadata: {
name: string
description?: string
origin?: string
lastImported?: number
vfsMetadata?: Record<string, any>
}
}
/**
* Git branch information
*/
export interface GitBranch {
name: string
commitHash: string
isActive: boolean
}
/**
* Git commit information
*/
export interface GitCommit {
hash: string
message: string
author: string
email: string
timestamp: number
parent?: string
files: string[] // Changed files
}
/**
* Git file representation
*/
export interface GitFile {
path: string
content: Buffer
hash: string
mode: string // File permissions
size: number
lastModified: number
}
/**
* Export options
*/
export interface ExportOptions {
preserveMetadata?: boolean // Export VFS metadata as .vfs-meta files
preserveRelationships?: boolean // Export relationships as .vfs-relations files
preserveHistory?: boolean // Export event history
branch?: string // Target branch name
commitMessage?: string // Commit message
author?: {
name: string
email: string
}
includeSystemFiles?: boolean // Include .vfs-* files
}
/**
* Import options
*/
export interface ImportOptions {
preserveGitHistory?: boolean // Import Git commits as VFS events
extractMetadata?: boolean // Extract metadata from .vfs-meta files
restoreRelationships?: boolean // Restore relationships from .vfs-relations files
includeSystemFiles?: boolean // Include .vfs- system files
branch?: string // Which branch to import
since?: number // Import commits since timestamp
author?: string // Only import commits from this author
}
/**
* Git Bridge - Import/Export between VFS and Git repositories
*
* Capabilities:
* - Export VFS to standard Git repository structure
* - Import Git repository into VFS with intelligence
* - Preserve VFS metadata and relationships
* - Convert Git history to VFS events
* - No Git dependencies - pure filesystem operations
*/
export class GitBridge {
constructor(
private vfs: VirtualFileSystem,
private brain: Brainy
) {}
/**
* Export VFS to Git repository structure
*/
async exportToGit(
vfsPath: string,
gitRepoPath: string,
options?: ExportOptions
): Promise<GitRepository> {
// Ensure target directory exists
await fs.mkdir(gitRepoPath, { recursive: true })
const exportId = uuidv4()
const timestamp = Date.now()
const files: GitFile[] = []
// Initialize Git repository metadata
const gitRepo: GitRepository = {
path: gitRepoPath,
branches: [{ name: options?.branch || 'main', commitHash: '', isActive: true }],
commits: [],
files: [],
metadata: {
name: path.basename(gitRepoPath),
description: `Exported from Brainy VFS on ${new Date().toISOString()}`,
lastImported: timestamp,
vfsMetadata: {
exportId,
sourcePath: vfsPath,
options
}
}
}
// Export files recursively
await this.exportDirectory(vfsPath, gitRepoPath, '', files, options)
// Create .vfs-metadata file if preserving metadata
if (options?.preserveMetadata) {
await this.exportMetadata(vfsPath, gitRepoPath, options)
}
// Create .vfs-relationships file if preserving relationships
if (options?.preserveRelationships) {
await this.exportRelationships(vfsPath, gitRepoPath, options)
}
// Create .vfs-history file if preserving history
if (options?.preserveHistory) {
await this.exportHistory(vfsPath, gitRepoPath, options)
}
// Create initial commit metadata
const commitHash = this.generateCommitHash(files)
const commit: GitCommit = {
hash: commitHash,
message: options?.commitMessage || `Export from Brainy VFS: ${vfsPath}`,
author: options?.author?.name || 'VFS Export',
email: options?.author?.email || 'vfs@brainy.local',
timestamp,
files: files.map(f => f.path)
}
gitRepo.commits = [commit]
gitRepo.branches[0].commitHash = commitHash
gitRepo.files = files
// Write repository metadata
await this.writeRepoMetadata(gitRepoPath, gitRepo)
// Record export event
await this.recordGitEvent('export', {
vfsPath,
gitRepoPath,
commitHash,
fileCount: files.length,
options
})
return gitRepo
}
/**
* Import Git repository into VFS
*/
async importFromGit(
gitRepoPath: string,
vfsPath: string,
options?: ImportOptions
): Promise<{
filesImported: number
eventsCreated: number
entitiesCreated: number
relationshipsCreated: number
}> {
const stats = {
filesImported: 0,
eventsCreated: 0,
entitiesCreated: 0,
relationshipsCreated: 0
}
// Read repository structure
const gitRepo = await this.readGitRepository(gitRepoPath)
// Import files
for (const gitFile of gitRepo.files) {
// Skip system files unless requested
if (!options?.includeSystemFiles && gitFile.path.startsWith('.vfs-')) {
continue
}
const targetPath = path.join(vfsPath, gitFile.path)
// Create directory structure
const dirPath = path.dirname(targetPath)
if (dirPath !== vfsPath) {
try {
await this.vfs.mkdir(dirPath, { recursive: true })
} catch (error) {
// Directory might already exist
}
}
// Write file
await this.vfs.writeFile(targetPath, gitFile.content, {
metadata: {
gitHash: gitFile.hash,
gitMode: gitFile.mode,
importedFrom: gitRepoPath,
importedAt: Date.now()
}
})
stats.filesImported++
}
// Import metadata if available and requested
if (options?.extractMetadata) {
const metadataFile = path.join(gitRepoPath, '.vfs-metadata.json')
try {
const metadataContent = await fs.readFile(metadataFile, 'utf8')
const metadata = JSON.parse(metadataContent)
await this.importMetadata(vfsPath, metadata)
stats.entitiesCreated += Object.keys(metadata.entities || {}).length
} catch (error) {
// No metadata file or invalid format
}
}
// Import relationships if available and requested
if (options?.restoreRelationships) {
const relationshipsFile = path.join(gitRepoPath, '.vfs-relationships.json')
try {
const relationshipsContent = await fs.readFile(relationshipsFile, 'utf8')
const relationships = JSON.parse(relationshipsContent)
await this.importRelationships(relationships)
stats.relationshipsCreated += relationships.length || 0
} catch (error) {
// No relationships file or invalid format
}
}
// Import Git history as VFS events if requested
if (options?.preserveGitHistory) {
const historyFile = path.join(gitRepoPath, '.vfs-history.json')
try {
const historyContent = await fs.readFile(historyFile, 'utf8')
const history = JSON.parse(historyContent)
stats.eventsCreated = await this.importHistory(vfsPath, history)
} catch (error) {
// Convert Git commits to VFS events
stats.eventsCreated = await this.convertCommitsToEvents(vfsPath, gitRepo.commits)
}
}
// Record import event
await this.recordGitEvent('import', {
gitRepoPath,
vfsPath,
stats,
options
})
return stats
}
/**
* Export directory recursively
*/
private async exportDirectory(
vfsPath: string,
gitRepoPath: string,
relativePath: string,
files: GitFile[],
options?: ExportOptions
): Promise<void> {
const currentPath = relativePath ? path.join(vfsPath, relativePath) : vfsPath
try {
// Check if path exists in VFS
const exists = await this.vfs.exists(currentPath)
if (!exists) return
// Get directory contents
const entries = await this.vfs.readdir(currentPath, { withFileTypes: true }) as VFSDirent[]
for (const entry of entries) {
const entryVfsPath = path.join(currentPath, entry.name)
const entryRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name
const entryGitPath = path.join(gitRepoPath, entryRelativePath)
if (entry.type === 'directory') {
// Create directory in Git repo
await fs.mkdir(entryGitPath, { recursive: true })
// Recurse into subdirectory
await this.exportDirectory(vfsPath, gitRepoPath, entryRelativePath, files, options)
} else {
// Export file
const content = await this.vfs.readFile(entryVfsPath)
const stats = await this.vfs.stat(entryVfsPath)
// Write file to Git repo
await fs.writeFile(entryGitPath, content)
// Add to files list
const gitFile: GitFile = {
path: entryRelativePath,
content,
hash: createHash('sha1').update(content).digest('hex'),
mode: stats.mode?.toString(8) || '100644',
size: content.length,
lastModified: stats.mtime?.getTime() || Date.now()
}
files.push(gitFile)
}
}
} catch (error) {
console.warn(`Failed to export directory ${currentPath}:`, error)
}
}
/**
* Export VFS metadata to .vfs-metadata.json
*/
private async exportMetadata(
vfsPath: string,
gitRepoPath: string,
options?: ExportOptions
): Promise<void> {
const metadata = {
exportedAt: Date.now(),
vfsPath,
version: '1.0',
entities: {},
files: {}
}
// Collect metadata for all files
await this.collectFileMetadata(vfsPath, '', metadata)
// Write metadata file
const metadataPath = path.join(gitRepoPath, '.vfs-metadata.json')
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2))
}
/**
* Export relationships to .vfs-relationships.json
*/
private async exportRelationships(
vfsPath: string,
gitRepoPath: string,
options?: ExportOptions
): Promise<void> {
// Get all relationships for files in the VFS path
const relationships: any[] = []
try {
// Get relationships for the specified path and its children
const related = await this.vfs.getRelated(vfsPath)
if (related && related.length > 0) {
relationships.push({
path: vfsPath,
relationships: related
})
}
// If it's a directory, get relationships for all files within
const stats = await this.vfs.stat(vfsPath)
if (stats.isDirectory()) {
const files = await this.vfs.readdir(vfsPath, { recursive: true })
for (const file of files) {
const fileName = typeof file === 'string' ? file : file.name
const fullPath = path.join(vfsPath, fileName)
try {
const fileRelated = await this.vfs.getRelated(fullPath)
if (fileRelated && fileRelated.length > 0) {
relationships.push({
path: fullPath,
relationships: fileRelated
})
}
} catch (err) {
// Skip files without relationships
}
}
}
} catch (err) {
// Path might not have relationships
}
// Write relationships file
const relationshipsPath = path.join(gitRepoPath, '.vfs-relationships.json')
await fs.writeFile(relationshipsPath, JSON.stringify(relationships, null, 2))
}
/**
* Export history to .vfs-history.json
*/
private async exportHistory(
vfsPath: string,
gitRepoPath: string,
options?: ExportOptions
): Promise<void> {
// Get event history for the VFS path
const history = {
exportedAt: Date.now(),
vfsPath,
events: [] as any[]
}
// Get history if Knowledge Layer is enabled
if ('getHistory' in this.vfs && typeof (this.vfs as any).getHistory === 'function') {
try {
const events = await (this.vfs as any).getHistory(vfsPath)
if (events) {
history.events = events
}
} catch (err) {
// Knowledge Layer might not be enabled
}
}
// Write history file
const historyPath = path.join(gitRepoPath, '.vfs-history.json')
await fs.writeFile(historyPath, JSON.stringify(history, null, 2))
}
/**
* Collect metadata recursively
*/
private async collectFileMetadata(
vfsPath: string,
relativePath: string,
metadata: any
): Promise<void> {
const currentPath = relativePath ? path.join(vfsPath, relativePath) : vfsPath
try {
const entries = await this.vfs.readdir(currentPath, { withFileTypes: true }) as VFSDirent[]
for (const entry of entries) {
const entryPath = path.join(currentPath, entry.name)
const entryRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name
if (entry.type === 'directory') {
await this.collectFileMetadata(vfsPath, entryRelativePath, metadata)
} else {
const stats = await this.vfs.stat(entryPath)
metadata.files[entryRelativePath] = {
size: stats.size,
mtime: stats.mtime,
ctime: stats.ctime,
mode: stats.mode,
metadata: (stats as any).metadata || {}
}
}
}
} catch (error) {
console.warn(`Failed to collect metadata for ${currentPath}:`, error)
}
}
/**
* Read Git repository structure
*/
private async readGitRepository(gitRepoPath: string): Promise<GitRepository> {
// Read repository metadata if available
const metadataPath = path.join(gitRepoPath, '.vfs-repo-metadata.json')
let metadata = {
name: path.basename(gitRepoPath),
description: 'Imported Git repository',
branches: [{ name: 'main', commitHash: '', isActive: true }],
commits: []
}
try {
const metadataContent = await fs.readFile(metadataPath, 'utf8')
metadata = { ...metadata, ...JSON.parse(metadataContent) }
} catch (error) {
// No metadata file available
}
// Scan directory for files
const files = await this.scanGitFiles(gitRepoPath, '')
const gitRepo: GitRepository = {
path: gitRepoPath,
branches: metadata.branches,
commits: metadata.commits,
files,
metadata
}
return gitRepo
}
/**
* Scan Git files recursively
*/
private async scanGitFiles(gitRepoPath: string, relativePath: string): Promise<GitFile[]> {
const files: GitFile[] = []
const currentPath = relativePath ? path.join(gitRepoPath, relativePath) : gitRepoPath
try {
const entries = await fs.readdir(currentPath, { withFileTypes: true })
for (const entry of entries) {
// Skip Git metadata directories
if (entry.name === '.git') continue
const entryPath = path.join(currentPath, entry.name)
const entryRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name
if (entry.isDirectory()) {
const subFiles = await this.scanGitFiles(gitRepoPath, entryRelativePath)
files.push(...subFiles)
} else {
const content = await fs.readFile(entryPath)
const stats = await fs.stat(entryPath)
const gitFile: GitFile = {
path: entryRelativePath,
content,
hash: createHash('sha1').update(content).digest('hex'),
mode: stats.mode.toString(8),
size: content.length,
lastModified: stats.mtime.getTime()
}
files.push(gitFile)
}
}
} catch (error) {
console.warn(`Failed to scan Git files in ${currentPath}:`, error)
}
return files
}
/**
* Import metadata from exported data
*/
private async importMetadata(vfsPath: string, metadata: any): Promise<void> {
// Apply metadata to imported files
for (const [filePath, fileMetadata] of Object.entries(metadata.files || {})) {
const fullPath = path.join(vfsPath, filePath)
try {
// Update file metadata if it exists
const exists = await this.vfs.exists(fullPath)
if (exists) {
// Would update file metadata in VFS
// This depends on VFS implementation supporting metadata updates
}
} catch (error) {
console.warn(`Failed to import metadata for ${fullPath}:`, error)
}
}
}
/**
* Import relationships
*/
private async importRelationships(relationships: any[]): Promise<void> {
for (const relationship of relationships) {
try {
await this.brain.relate({
from: relationship.from,
to: relationship.to,
type: relationship.type,
metadata: relationship.metadata
})
} catch (error) {
console.warn('Failed to import relationship:', error)
}
}
}
/**
* Import history from exported data
*/
private async importHistory(vfsPath: string, history: any): Promise<number> {
let eventsCreated = 0
for (const event of history.events || []) {
try {
await this.brain.add({
type: NounType.Event,
data: Buffer.from(JSON.stringify(event)),
metadata: {
...event,
importedFrom: 'git',
importedAt: Date.now()
}
})
eventsCreated++
} catch (error) {
console.warn('Failed to import history event:', error)
}
}
return eventsCreated
}
/**
* Convert Git commits to VFS events
*/
private async convertCommitsToEvents(vfsPath: string, commits: GitCommit[]): Promise<number> {
let eventsCreated = 0
for (const commit of commits) {
try {
await this.brain.add({
type: NounType.Event,
data: Buffer.from(commit.message),
metadata: {
eventType: 'git-commit',
gitHash: commit.hash,
author: commit.author,
email: commit.email,
timestamp: commit.timestamp,
files: commit.files,
vfsPath,
system: 'git-bridge'
}
})
eventsCreated++
} catch (error) {
console.warn('Failed to convert commit to event:', error)
}
}
return eventsCreated
}
/**
* Generate commit hash
*/
private generateCommitHash(files: GitFile[]): string {
const content = files.map(f => f.path + ':' + f.hash).join('\n')
return createHash('sha1').update(content).digest('hex')
}
/**
* Write repository metadata
*/
private async writeRepoMetadata(gitRepoPath: string, gitRepo: GitRepository): Promise<void> {
const metadataPath = path.join(gitRepoPath, '.vfs-repo-metadata.json')
await fs.writeFile(metadataPath, JSON.stringify(gitRepo.metadata, null, 2))
}
/**
* Record Git bridge event
*/
private async recordGitEvent(operation: 'export' | 'import', details: any): Promise<void> {
try {
await this.brain.add({
type: NounType.Event,
data: Buffer.from(JSON.stringify(details)),
metadata: {
eventType: 'git-bridge',
operation,
timestamp: Date.now(),
system: 'git-bridge',
...details
}
})
} catch (error) {
console.warn('Failed to record Git bridge event:', error)
}
}
}

View file

@ -1,542 +0,0 @@
/**
* Knowledge Layer for VFS
*
* This is the REAL integration that makes VFS intelligent.
* It wraps VFS operations and adds Knowledge Layer processing.
*/
import { VirtualFileSystem } from './VirtualFileSystem.js'
import { Brainy } from '../brainy.js'
import { EventRecorder } from './EventRecorder.js'
import { SemanticVersioning } from './SemanticVersioning.js'
import { PersistentEntitySystem } from './PersistentEntitySystem.js'
import { ConceptSystem } from './ConceptSystem.js'
import { GitBridge } from './GitBridge.js'
export class KnowledgeLayer {
private eventRecorder: EventRecorder
private semanticVersioning: SemanticVersioning
private entitySystem: PersistentEntitySystem
private conceptSystem: ConceptSystem
private gitBridge: GitBridge
private enabled = false
constructor(
private vfs: VirtualFileSystem,
private brain: Brainy
) {
// Initialize all Knowledge Layer components
this.eventRecorder = new EventRecorder(brain)
this.semanticVersioning = new SemanticVersioning(brain)
this.entitySystem = new PersistentEntitySystem(brain)
this.conceptSystem = new ConceptSystem(brain)
this.gitBridge = new GitBridge(vfs, brain)
}
/**
* Enable Knowledge Layer by wrapping VFS methods
*/
async enable(): Promise<void> {
if (this.enabled) return
this.enabled = true
// Save original methods
const originalWriteFile = this.vfs.writeFile.bind(this.vfs)
const originalUnlink = this.vfs.unlink.bind(this.vfs)
const originalRename = this.vfs.rename.bind(this.vfs)
const originalMkdir = this.vfs.mkdir.bind(this.vfs)
const originalRmdir = this.vfs.rmdir.bind(this.vfs)
// Wrap writeFile to add intelligence
this.vfs.writeFile = async (path: string, data: Buffer | string, options?: any) => {
// Call original VFS method first
const result = await originalWriteFile(path, data, options)
// Process in background (non-blocking)
setImmediate(async () => {
try {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
// 1. Record the event
await this.eventRecorder.recordEvent({
type: 'write',
path,
content: buffer,
size: buffer.length,
author: options?.author || 'system'
})
// 2. Check for semantic versioning
try {
const existingContent = await this.vfs.readFile(path).catch(() => null)
if (existingContent) {
const shouldVersion = await this.semanticVersioning.shouldVersion(existingContent, buffer)
if (shouldVersion) {
await this.semanticVersioning.createVersion(path, buffer, {
message: options?.message || 'Automatic semantic version'
})
}
}
} catch (err) {
console.debug('Versioning check failed:', err)
}
// 3. Extract entities
if (options?.extractEntities !== false) {
await this.entitySystem.extractEntities(path, buffer)
}
// 4. Extract concepts
if (options?.extractConcepts !== false) {
await this.conceptSystem.extractAndLinkConcepts(path, buffer)
}
} catch (error) {
console.debug('Knowledge Layer processing error:', error)
}
})
return result
}
// Wrap unlink to record deletion
this.vfs.unlink = async (path: string) => {
const result = await originalUnlink(path)
setImmediate(async () => {
await this.eventRecorder.recordEvent({
type: 'delete',
path,
author: 'system'
})
})
return result
}
// Wrap rename to track moves
this.vfs.rename = async (oldPath: string, newPath: string) => {
const result = await originalRename(oldPath, newPath)
setImmediate(async () => {
await this.eventRecorder.recordEvent({
type: 'rename',
path: oldPath,
metadata: { newPath },
author: 'system'
})
})
return result
}
// Wrap mkdir to track directory creation
this.vfs.mkdir = async (path: string, options?: any) => {
const result = await originalMkdir(path, options)
setImmediate(async () => {
await this.eventRecorder.recordEvent({
type: 'mkdir',
path,
author: 'system'
})
})
return result
}
// Wrap rmdir to track directory deletion
this.vfs.rmdir = async (path: string, options?: any) => {
const result = await originalRmdir(path, options)
setImmediate(async () => {
await this.eventRecorder.recordEvent({
type: 'rmdir',
path,
author: 'system'
})
})
return result
}
// Add Knowledge Layer methods to VFS
this.addKnowledgeMethods()
console.log('✨ Knowledge Layer enabled on VFS')
}
/**
* Add Knowledge Layer query methods to VFS
*/
private addKnowledgeMethods(): void {
// Event history
(this.vfs as any).getHistory = async (path: string, options?: any) => {
return await this.eventRecorder.getHistory(path, options)
}
(this.vfs as any).reconstructAtTime = async (path: string, timestamp: number) => {
return await this.eventRecorder.reconstructFileAtTime(path, timestamp)
}
// Semantic versioning
(this.vfs as any).getVersions = async (path: string) => {
return await this.semanticVersioning.getVersions(path)
}
(this.vfs as any).getVersion = async (path: string, versionId: string) => {
return await this.semanticVersioning.getVersion(path, versionId)
}
(this.vfs as any).restoreVersion = async (path: string, versionId: string) => {
const content = await this.semanticVersioning.getVersion(path, versionId)
if (content) {
await this.vfs.writeFile(path, content)
}
}
// Entity system
(this.vfs as any).createEntity = async (config: any) => {
return await this.entitySystem.createEntity(config)
}
(this.vfs as any).findEntity = async (query: any) => {
return await this.entitySystem.findEntity(query)
}
(this.vfs as any).getEntityEvolution = async (entityId: string) => {
return await this.entitySystem.getEvolution(entityId)
}
// Concept system
(this.vfs as any).createConcept = async (config: any) => {
return await this.conceptSystem.createConcept(config)
}
(this.vfs as any).findConcepts = async (query: any) => {
return await this.conceptSystem.findConcepts(query)
}
(this.vfs as any).getConceptGraph = async (options?: any) => {
return await this.conceptSystem.getConceptGraph(options)
}
// Git bridge
(this.vfs as any).exportToGit = async (vfsPath: string, gitPath: string) => {
return await this.gitBridge.exportToGit(vfsPath, gitPath)
}
(this.vfs as any).importFromGit = async (gitPath: string, vfsPath: string) => {
return await this.gitBridge.importFromGit(gitPath, vfsPath)
}
// Temporal coupling
(this.vfs as any).findTemporalCoupling = async (path: string, windowMs?: number) => {
return await this.eventRecorder.findTemporalCoupling(path, windowMs)
}
// Entity convenience methods that wrap Brainy's core API
(this.vfs as any).linkEntities = async (fromEntity: string | any, toEntity: string | any, relationship: string) => {
// Handle both entity IDs and entity objects
const fromId = typeof fromEntity === 'string' ? fromEntity : fromEntity.id
const toId = typeof toEntity === 'string' ? toEntity : toEntity.id
// Use brain.relate to create the relationship
return await this.brain.relate({
from: fromId,
to: toId,
type: relationship as any // VerbType or string
})
}
// Find where an entity appears across files
(this.vfs as any).findEntityOccurrences = async (entityId: string) => {
const occurrences: Array<{ path: string, context?: string }> = []
// Search for files that contain references to this entity
// First, get all relationships where this entity is involved
const relations = await this.brain.getRelations({ from: entityId })
const toRelations = await this.brain.getRelations({ to: entityId })
// Find file entities that relate to this entity
for (const rel of [...relations, ...toRelations]) {
try {
// Check if the related entity is a file
const relatedId = rel.from === entityId ? rel.to : rel.from
const entity = await this.brain.get(relatedId)
if (entity?.metadata?.vfsType === 'file' && entity?.metadata?.path) {
occurrences.push({
path: entity.metadata.path as string,
context: entity.data ? entity.data.toString().substring(0, 200) : undefined
})
}
} catch (error) {
// Entity might not exist, continue
}
}
// Also search for files that mention the entity name in their content
const entityData = await this.brain.get(entityId)
if (entityData?.metadata?.name) {
const searchResults = await this.brain.find({
query: entityData.metadata.name as string,
where: { vfsType: 'file' },
limit: 20
})
for (const result of searchResults) {
if (result.entity?.metadata?.path && !occurrences.some(o => o.path === result.entity.metadata.path)) {
occurrences.push({
path: result.entity.metadata.path as string,
context: result.entity.data ? result.entity.data.toString().substring(0, 200) : undefined
})
}
}
}
return occurrences
}
// Update an entity (convenience wrapper)
(this.vfs as any).updateEntity = async (entityId: string, updates: any) => {
// Get current entity from brain
const currentEntity = await this.brain.get(entityId)
if (!currentEntity) {
throw new Error(`Entity ${entityId} not found`)
}
// Merge updates
const updatedMetadata = {
...currentEntity.metadata,
...updates,
lastUpdated: Date.now(),
version: ((currentEntity.metadata?.version as number) || 0) + 1
}
// Update via brain
await this.brain.update({
id: entityId,
data: JSON.stringify(updatedMetadata),
metadata: updatedMetadata
})
return entityId
}
// Get entity graph (convenience wrapper)
(this.vfs as any).getEntityGraph = async (entityId: string, options?: { depth?: number }) => {
const depth = options?.depth || 2
const graph = { nodes: new Map(), edges: [] as any[] }
const visited = new Set<string>()
const traverse = async (id: string, currentDepth: number) => {
if (visited.has(id) || currentDepth > depth) return
visited.add(id)
// Add node
const entity = await this.brain.get(id)
if (entity) {
graph.nodes.set(id, entity)
}
// Get relationships
const relations = await this.brain.getRelations({ from: id })
const toRelations = await this.brain.getRelations({ to: id })
for (const rel of [...relations, ...toRelations]) {
graph.edges.push(rel)
// Traverse connected nodes
if (currentDepth < depth) {
const nextId = rel.from === id ? rel.to : rel.from
await traverse(nextId, currentDepth + 1)
}
}
}
await traverse(entityId, 0)
return {
nodes: Array.from(graph.nodes.values()),
edges: graph.edges
}
}
// List all entities of a specific type
(this.vfs as any).listEntities = async (query?: { type?: string }) => {
return await this.entitySystem.findEntity(query || {})
}
// Find files by concept
(this.vfs as any).findByConcept = async (conceptName: string): Promise<string[]> => {
const paths: string[] = []
// First find the concept
const concepts = await this.conceptSystem.findConcepts({ name: conceptName })
if (concepts.length === 0) {
return paths
}
const concept = concepts[0]
// Search for files that contain concept keywords
const searchTerms = [conceptName, ...(concept.keywords || [])].join(' ')
const searchResults = await this.brain.find({
query: searchTerms,
where: { vfsType: 'file' },
limit: 50
})
// Get unique paths
const pathSet = new Set<string>()
for (const result of searchResults) {
if (result.entity?.metadata?.path) {
pathSet.add(result.entity.metadata.path as string)
}
}
// Also check concept manifestations if stored
if (concept.manifestations) {
for (const manifestation of concept.manifestations) {
if (manifestation.filePath) {
pathSet.add(manifestation.filePath)
}
}
}
return Array.from(pathSet)
}
// Get timeline of events
(this.vfs as any).getTimeline = async (options?: {
from?: string | Date | number
to?: string | Date | number
types?: string[]
limit?: number
}): Promise<Array<{
timestamp: Date
type: string
path: string
user?: string
description: string
}>> => {
const fromTime = options?.from ? new Date(options.from).getTime() : Date.now() - 30 * 24 * 60 * 60 * 1000 // 30 days ago
const toTime = options?.to ? new Date(options.to).getTime() : Date.now()
const limit = options?.limit || 100
// Get events from event recorder
const events = await this.eventRecorder.getEvents({
since: fromTime,
until: toTime,
types: options?.types,
limit
})
// Transform to timeline format
return events.map(event => ({
timestamp: new Date(event.timestamp),
type: event.type,
path: event.path,
user: event.author,
description: `${event.type} ${event.path}${event.oldPath ? ` (from ${event.oldPath})` : ''}`
}))
}
// Get collaboration history for a file
(this.vfs as any).getCollaborationHistory = async (path: string): Promise<Array<{
user: string
timestamp: Date
action: string
size?: number
}>> => {
// Get all events for this path
const history = await this.eventRecorder.getHistory(path, { limit: 100 })
return history.map(event => ({
user: event.author || 'system',
timestamp: new Date(event.timestamp),
action: event.type,
size: event.size
}))
}
// Export to markdown format
(this.vfs as any).exportToMarkdown = async (path: string): Promise<string> => {
const markdown: string[] = []
const traverse = async (currentPath: string, depth: number = 0) => {
const indent = ' '.repeat(depth)
try {
const stats = await this.vfs.stat(currentPath)
if (stats.isDirectory()) {
// Add directory header
const name = currentPath.split('/').pop() || currentPath
markdown.push(`${indent}## ${name}/`)
markdown.push('')
// List and traverse children
const children = await this.vfs.readdir(currentPath)
for (const child of children.sort()) {
const childPath = currentPath === '/' ? `/${child}` : `${currentPath}/${child}`
await traverse(childPath, depth + 1)
}
} else {
// Add file content
const name = currentPath.split('/').pop() || currentPath
const extension = name.split('.').pop() || 'txt'
try {
const content = await this.vfs.readFile(currentPath)
const textContent = content.toString('utf8')
markdown.push(`${indent}### ${name}`)
markdown.push('')
// Add code block for code files
if (['js', 'ts', 'jsx', 'tsx', 'py', 'java', 'cpp', 'go', 'rs', 'json'].includes(extension)) {
markdown.push(`${indent}\`\`\`${extension}`)
markdown.push(textContent.split('\n').map(line => `${indent}${line}`).join('\n'))
markdown.push(`${indent}\`\`\``)
} else if (extension === 'md') {
// Include markdown directly
markdown.push(textContent)
} else {
// Plain text in quotes
markdown.push(`${indent}> ${textContent.split('\n').join(`\n${indent}> `)}`)
}
markdown.push('')
} catch (error) {
markdown.push(`${indent}*Binary or unreadable file*`)
markdown.push('')
}
}
} catch (error) {
// Skip inaccessible paths
}
}
await traverse(path)
return markdown.join('\n')
}
}
/**
* Disable Knowledge Layer
*/
async disable(): Promise<void> {
// Would restore original methods here
this.enabled = false
}
}
/**
* Enable Knowledge Layer on a VFS instance
*/
export async function enableKnowledgeLayer(vfs: VirtualFileSystem, brain: Brainy): Promise<KnowledgeLayer> {
const knowledgeLayer = new KnowledgeLayer(vfs, brain)
await knowledgeLayer.enable()
return knowledgeLayer
}

View file

@ -1,689 +0,0 @@
/**
* Persistent Entity System for VFS
*
* Manages entities that evolve across files and time
* Not just story characters - any evolving entity: APIs, customers, services, models
* PRODUCTION-READY: Real implementation using Brainy
*/
import { Brainy } from '../brainy.js'
import { NounType, VerbType } from '../types/graphTypes.js'
import { cosineDistance } from '../utils/distance.js'
import { v4 as uuidv4 } from '../universal/uuid.js'
import { EntityManager, ManagedEntity } from './EntityManager.js'
/**
* Persistent entity that exists across files and evolves over time
*/
export interface PersistentEntity extends ManagedEntity {
id: string
name: string
type: string // 'character', 'api', 'service', 'concept', 'customer', etc.
description?: string
aliases: string[] // Alternative names/references
appearances: EntityAppearance[]
attributes: Record<string, any>
created: number
lastUpdated: number
version: number
entityType?: string // For EntityManager queries
}
/**
* An appearance of an entity in a specific file/location
*/
export interface EntityAppearance extends ManagedEntity {
id: string
entityId: string
filePath: string
context: string // Surrounding text/code
position?: {
line?: number
column?: number
offset?: number
}
timestamp: number
version: number
changes?: EntityChange[]
confidence: number // How confident we are this is the entity (0-1)
eventType?: string // For EntityManager queries
}
/**
* A change/evolution to an entity
*/
export interface EntityChange {
field: string
oldValue: any
newValue: any
timestamp: number
source: string // File path where change was detected
reason?: string // Why the change was made
}
/**
* Configuration for persistent entities
*/
export interface PersistentEntityConfig {
autoExtract?: boolean // Auto-extract entities from content
similarityThreshold?: number // For entity matching (0-1)
maxAppearances?: number // Max appearances to track per entity
evolutionTracking?: boolean // Track entity evolution
}
/**
* Persistent Entity System
*
* Tracks entities that exist across multiple files and evolve over time
* Examples:
* - Story characters that appear in multiple chapters
* - API endpoints that evolve across documentation
* - Business entities that appear in multiple reports
* - Code classes/functions that span multiple files
*/
export class PersistentEntitySystem extends EntityManager {
private config: Required<PersistentEntityConfig>
private entityCache = new Map<string, PersistentEntity>()
constructor(
brain: Brainy,
config?: PersistentEntityConfig
) {
super(brain, 'vfs-entity')
this.config = {
autoExtract: config?.autoExtract ?? false,
similarityThreshold: config?.similarityThreshold ?? 0.8,
maxAppearances: config?.maxAppearances ?? 100,
evolutionTracking: config?.evolutionTracking ?? true
}
}
/**
* Create a new persistent entity
*/
async createEntity(entity: Omit<PersistentEntity, 'id' | 'created' | 'lastUpdated' | 'version' | 'appearances'>): Promise<string> {
const entityId = uuidv4()
const timestamp = Date.now()
const persistentEntity: PersistentEntity = {
id: entityId,
name: entity.name,
type: entity.type,
description: entity.description,
aliases: entity.aliases,
attributes: entity.attributes,
created: timestamp,
lastUpdated: timestamp,
version: 1,
appearances: [],
entityType: 'persistent' // Add entityType for querying
}
// Generate embedding for entity description
let embedding: number[] | undefined
try {
if (entity.description) {
embedding = await this.generateEntityEmbedding(persistentEntity)
}
} catch (error) {
console.warn('Failed to generate entity embedding:', error)
}
// Store entity using EntityManager
await this.storeEntity(
persistentEntity,
NounType.Concept,
embedding,
Buffer.from(JSON.stringify(persistentEntity))
)
// Update cache
this.entityCache.set(entityId, persistentEntity)
return entityId // Return domain ID, not Brainy ID
}
/**
* Find an existing entity by name or attributes
*/
async findEntity(query: {
name?: string
type?: string
attributes?: Record<string, any>
similar?: string // Find similar entities to this description
}): Promise<PersistentEntity[]> {
const searchQuery: any = {}
if (query.name) {
// Search by exact name or aliases
searchQuery.$or = [
{ name: query.name },
{ aliases: { $in: [query.name] } }
]
}
if (query.type) {
searchQuery.type = query.type
}
if (query.attributes) {
for (const [key, value] of Object.entries(query.attributes)) {
searchQuery[`attributes.${key}`] = value
}
}
// Add system metadata for EntityManager
searchQuery.entityType = 'persistent'
// Search using EntityManager
let results = await this.findEntities<PersistentEntity>(searchQuery, NounType.Concept, 100)
// If searching for similar entities, use vector similarity
if (query.similar) {
try {
const queryEmbedding = await this.generateTextEmbedding(query.similar)
if (queryEmbedding) {
// Get all entities and rank by similarity using EntityManager
const allEntities = await this.findEntities<PersistentEntity>({}, NounType.Concept, 1000)
const withSimilarity = allEntities
.filter(e => e.brainyId) // Only entities with brainyId have vectors
.map(async e => {
const brainyEntity = await this.brain.get(e.brainyId!)
if (brainyEntity?.vector) {
return {
entity: e,
similarity: 1 - cosineDistance(queryEmbedding, brainyEntity.vector)
}
}
return null
})
const resolved = (await Promise.all(withSimilarity))
.filter(s => s !== null && s.similarity > this.config.similarityThreshold)
.sort((a, b) => b!.similarity - a!.similarity)
results = resolved.map(s => s!.entity)
}
} catch (error) {
console.warn('Failed to perform similarity search:', error)
}
}
return results
}
/**
* Record an appearance of an entity in a file
*/
async recordAppearance(
entityId: string,
filePath: string,
context: string,
options?: {
position?: EntityAppearance['position']
confidence?: number
extractChanges?: boolean
}
): Promise<string> {
const entity = await this.getPersistentEntity(entityId)
if (!entity) {
throw new Error(`Entity ${entityId} not found`)
}
const appearanceId = uuidv4()
const timestamp = Date.now()
// Detect changes if enabled
let changes: EntityChange[] = []
if (options?.extractChanges && this.config.evolutionTracking) {
changes = await this.detectChanges(entity, context, filePath)
}
const appearance: EntityAppearance = {
id: appearanceId,
entityId,
filePath,
context,
position: options?.position,
timestamp,
version: entity.version,
changes,
confidence: options?.confidence ?? 1.0
}
// Store appearance as managed entity (with eventType for appearances)
const appearanceWithEventType = {
...appearance,
eventType: 'entity-appearance'
}
await this.storeEntity(
appearanceWithEventType,
NounType.Event,
undefined,
Buffer.from(context)
)
// Create relationship to entity using EntityManager
await this.createRelationship(
appearanceId,
entityId,
VerbType.References
)
// Update entity with new appearance
entity.appearances.push(appearance)
entity.lastUpdated = timestamp
// Apply changes if any detected
if (changes.length > 0) {
entity.version++
for (const change of changes) {
if (change.field in entity.attributes) {
entity.attributes[change.field] = change.newValue
}
}
}
// Prune old appearances if needed
if (entity.appearances.length > this.config.maxAppearances) {
entity.appearances = entity.appearances
.sort((a, b) => b.timestamp - a.timestamp)
.slice(0, this.config.maxAppearances)
}
// Update stored entity
await this.updatePersistentEntity(entity)
return appearanceId
}
/**
* Get entity evolution history
*/
async getEvolution(entityId: string): Promise<{
entity: PersistentEntity
timeline: Array<{
timestamp: number
version: number
changes: EntityChange[]
appearance?: EntityAppearance
}>
}> {
const entity = await this.getPersistentEntity(entityId)
if (!entity) {
throw new Error(`Entity ${entityId} not found`)
}
// Get all appearances sorted by time
const appearances = entity.appearances.sort((a, b) => a.timestamp - b.timestamp)
// Build timeline
const timeline = []
for (const appearance of appearances) {
if (appearance.changes && appearance.changes.length > 0) {
timeline.push({
timestamp: appearance.timestamp,
version: appearance.version,
changes: appearance.changes,
appearance
})
}
}
return { entity, timeline }
}
/**
* Find all appearances of an entity
*/
async findAppearances(entityId: string, options?: {
filePath?: string
since?: number
until?: number
minConfidence?: number
}): Promise<EntityAppearance[]> {
const query: any = {
entityId,
eventType: 'entity-appearance',
system: 'vfs-entity'
}
if (options?.filePath) {
query.filePath = options.filePath
}
if (options?.since || options?.until) {
query.timestamp = {}
if (options.since) query.timestamp.$gte = options.since
if (options.until) query.timestamp.$lte = options.until
}
if (options?.minConfidence) {
query.confidence = { $gte: options.minConfidence }
}
const results = await this.brain.find({
where: query,
type: NounType.Event,
limit: 1000
})
return results
.map(r => r.entity.metadata as EntityAppearance)
.sort((a, b) => b.timestamp - a.timestamp)
}
/**
* Evolve an entity with new information
*/
async evolveEntity(
entityId: string,
updates: Partial<Pick<PersistentEntity, 'name' | 'description' | 'aliases' | 'attributes'>>,
source: string,
reason?: string
): Promise<void> {
const entity = await this.getPersistentEntity(entityId)
if (!entity) {
throw new Error(`Entity ${entityId} not found`)
}
const timestamp = Date.now()
const changes: EntityChange[] = []
// Track changes
for (const [field, newValue] of Object.entries(updates)) {
if (field in entity && entity[field as keyof PersistentEntity] !== newValue) {
changes.push({
field,
oldValue: entity[field as keyof PersistentEntity],
newValue,
timestamp,
source,
reason
})
}
}
// Apply updates
Object.assign(entity, updates)
entity.lastUpdated = timestamp
entity.version++
// Update stored entity
await this.updatePersistentEntity(entity)
// Record evolution event
if (changes.length > 0) {
await this.brain.add({
type: NounType.Event,
data: Buffer.from(JSON.stringify(changes)),
metadata: {
entityId,
changes,
timestamp,
source,
reason,
eventType: 'entity-evolution',
system: 'vfs-entity'
}
})
}
}
/**
* Extract entities from content (auto-extraction)
*/
async extractEntities(filePath: string, content: Buffer): Promise<string[]> {
if (!this.config.autoExtract) {
return []
}
// Convert content to text for processing
const text = content.toString('utf8')
const entities: string[] = []
// Simple entity extraction patterns
// In production, this would use NLP/ML models
const patterns = [
// Character names (capitalized words)
/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b/g,
// API endpoints
/\/api\/[a-zA-Z0-9/\-_]+/g,
// Class names
/class\s+([A-Z][a-zA-Z0-9_]*)/g,
// Function names
/function\s+([a-zA-Z_][a-zA-Z0-9_]*)/g
]
for (const pattern of patterns) {
const matches = text.matchAll(pattern)
for (const match of matches) {
const entityName = match[1] || match[0]
// Check if entity already exists
const existing = await this.findEntity({ name: entityName })
if (existing.length === 0) {
// Create new entity
const entityId = await this.createEntity({
name: entityName,
type: this.detectEntityType(entityName, text),
aliases: [],
attributes: {}
})
entities.push(entityId)
}
// Record appearance for existing or new entity
const entity = existing[0] || await this.getPersistentEntity(entities[entities.length - 1])
if (entity) {
await this.recordAppearance(
entity.id,
filePath,
this.extractContext(text, match.index || 0),
{ confidence: 0.7, extractChanges: true }
)
}
}
}
return entities
}
/**
* Update references when a file moves
*/
async updateReferences(oldPath: string, newPath: string): Promise<void> {
// Find all appearances in the old path
const results = await this.brain.find({
where: {
filePath: oldPath,
eventType: 'entity-appearance',
system: 'vfs-entity'
},
type: NounType.Event,
limit: 10000
})
// Update each appearance
for (const result of results) {
const appearance = result.entity.metadata as EntityAppearance
appearance.filePath = newPath
// Update the stored appearance
await this.brain.update({
id: result.entity.id,
metadata: appearance
})
}
// Update cache
for (const entity of this.entityCache.values()) {
for (const appearance of entity.appearances) {
if (appearance.filePath === oldPath) {
appearance.filePath = newPath
}
}
}
}
/**
* Get persistent entity by ID
*/
async getPersistentEntity(entityId: string): Promise<PersistentEntity | null> {
// Check cache first
if (this.entityCache.has(entityId)) {
return this.entityCache.get(entityId)!
}
// Use parent getEntity method
const entity = await super.getEntity<PersistentEntity>(entityId)
if (entity) {
// Cache and return
this.entityCache.set(entityId, entity)
}
return entity
}
/**
* Update stored entity (rename to avoid parent method conflict)
*/
private async updatePersistentEntity(entity: PersistentEntity): Promise<void> {
// Find the Brainy entity
const results = await this.brain.find({
where: {
id: entity.id,
entityType: 'persistent',
system: 'vfs-entity'
},
type: NounType.Concept,
limit: 1
})
if (results.length > 0) {
await this.brain.update({
id: results[0].entity.id,
data: Buffer.from(JSON.stringify(entity)),
metadata: {
...entity,
entityType: 'persistent',
system: 'vfs-entity'
}
})
}
// Update cache
this.entityCache.set(entity.id, entity)
}
/**
* Detect changes in entity from context
*/
private async detectChanges(
entity: PersistentEntity,
context: string,
source: string
): Promise<EntityChange[]> {
// Simple change detection - in production would use NLP
const changes: EntityChange[] = []
const timestamp = Date.now()
// Look for attribute changes in context
const attributePatterns = [
/(\w+):\s*"([^"]+)"/g, // key: "value"
/(\w+)\s*=\s*"([^"]+)"/g, // key = "value"
/set(\w+)\("([^"]+)"\)/g // setProperty("value")
]
for (const pattern of attributePatterns) {
const matches = context.matchAll(pattern)
for (const match of matches) {
const field = match[1].toLowerCase()
const newValue = match[2]
if (field in entity.attributes && entity.attributes[field] !== newValue) {
changes.push({
field: `attributes.${field}`,
oldValue: entity.attributes[field],
newValue,
timestamp,
source
})
}
}
}
return changes
}
/**
* Generate embedding for entity
*/
private async generateEntityEmbedding(entity: PersistentEntity): Promise<number[] | undefined> {
try {
// Create text representation of entity
const text = [
entity.name,
entity.description || '',
entity.type,
...entity.aliases,
JSON.stringify(entity.attributes)
].join(' ')
return await this.generateTextEmbedding(text)
} catch (error) {
console.error('Failed to generate entity embedding:', error)
return undefined
}
}
/**
* Generate embedding for text
*/
private async generateTextEmbedding(text: string): Promise<number[] | undefined> {
try {
// Generate embedding using Brainy's embed method
const vector = await this.brain.embed(text)
return vector
} catch (error) {
console.debug('Failed to generate embedding:', error)
return undefined
}
}
/**
* Detect entity type from name and context
*/
private detectEntityType(name: string, context: string): string {
if (context.includes('class ' + name)) return 'class'
if (context.includes('function ' + name)) return 'function'
if (context.includes('/api/')) return 'api'
if (/^[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*$/.test(name)) return 'person'
return 'entity'
}
/**
* Extract context around a position
*/
private extractContext(text: string, position: number, radius = 100): string {
const start = Math.max(0, position - radius)
const end = Math.min(text.length, position + radius)
return text.slice(start, end)
}
/**
* Clear entity cache
*/
clearCache(entityId?: string): void {
if (entityId) {
this.entityCache.delete(entityId)
} else {
this.entityCache.clear()
}
}
}

View file

@ -1,404 +0,0 @@
/**
* Semantic Versioning System for VFS
*
* Only creates versions when the MEANING of content changes significantly
* PRODUCTION-READY: Real implementation using embeddings
*/
import { Brainy } from '../brainy.js'
import { NounType, VerbType } from '../types/graphTypes.js'
import { cosineDistance } from '../utils/distance.js'
import { createHash } from 'crypto'
import { v4 as uuidv4 } from '../universal/uuid.js'
import { EntityManager, ManagedEntity } from './EntityManager.js'
/**
* Version metadata
*/
export interface Version extends ManagedEntity {
id: string
path: string
version: number
timestamp: number
hash: string
size: number
semanticHash?: string // Hash of the embedding for quick comparison
author?: string
message?: string
parentVersion?: string
}
/**
* Semantic versioning configuration
*/
export interface SemanticVersioningConfig {
threshold?: number // Semantic change threshold (0-1, default 0.3)
maxVersions?: number // Max versions to keep per file
minInterval?: number // Minimum time between versions (ms)
sizeChangeThreshold?: number // Size change threshold (0-1)
}
/**
* Semantic Versioning System
*
* Creates versions only when content meaning changes significantly
* Uses vector embeddings to detect semantic changes
*/
export class SemanticVersioning extends EntityManager {
private config: Required<SemanticVersioningConfig>
private versionCache = new Map<string, Version[]>()
constructor(
brain: Brainy,
config?: SemanticVersioningConfig
) {
super(brain, 'vfs-version')
this.config = {
threshold: config?.threshold ?? 0.3,
maxVersions: config?.maxVersions ?? 10,
minInterval: config?.minInterval ?? 60000, // 1 minute
sizeChangeThreshold: config?.sizeChangeThreshold ?? 0.5
}
}
/**
* Check if content has changed enough to warrant a new version
*/
async shouldVersion(oldContent: Buffer, newContent: Buffer): Promise<boolean> {
// Quick hash check - if identical, no version needed
const oldHash = this.hashContent(oldContent)
const newHash = this.hashContent(newContent)
if (oldHash === newHash) {
return false
}
// Check size change
const sizeChange = Math.abs(oldContent.length - newContent.length) / Math.max(oldContent.length, 1)
if (sizeChange > this.config.sizeChangeThreshold) {
return true // Large size change warrants version
}
// For small files, any change is significant
if (oldContent.length < 100 || newContent.length < 100) {
return true
}
// Check semantic change using embeddings
try {
const semanticDistance = await this.calculateSemanticDistance(oldContent, newContent)
return semanticDistance > this.config.threshold
} catch (error) {
// If embedding fails, fall back to size-based decision
console.warn('Failed to calculate semantic distance:', error)
return sizeChange > 0.2
}
}
/**
* Create a new version
*/
async createVersion(
path: string,
content: Buffer,
metadata?: {
author?: string
message?: string
}
): Promise<string> {
const versionId = uuidv4()
const timestamp = Date.now()
const hash = this.hashContent(content)
// Get current version number
const versions = await this.getVersions(path)
const versionNumber = versions.length + 1
const parentVersion = versions[0]?.id
// Generate embedding for semantic comparison
let embedding: number[] | undefined
let semanticHash: string | undefined
try {
// Only generate embedding for reasonably sized content
if (content.length < 100000) {
embedding = await this.generateEmbedding(content)
if (embedding) {
semanticHash = this.hashEmbedding(embedding)
}
}
} catch (error) {
console.warn('Failed to generate embedding for version:', error)
}
// Create version entity
const version: Version = {
id: versionId,
path,
version: versionNumber,
timestamp,
hash,
semanticHash,
size: content.length,
author: metadata?.author,
message: metadata?.message,
parentVersion
}
// Store version using EntityManager (with actual content as data)
await this.storeEntity(version, NounType.State, embedding, content)
// Create relationship to parent version if exists
if (parentVersion) {
try {
await this.createRelationship(versionId, parentVersion, VerbType.Succeeds)
} catch (error) {
console.warn(`Failed to create parent relationship for version ${versionId}:`, error)
// Continue without relationship - non-critical for version functionality
}
}
// Update cache
if (!this.versionCache.has(path)) {
this.versionCache.set(path, [])
}
this.versionCache.get(path)!.unshift({
id: versionId,
path,
version: versionNumber,
timestamp,
hash,
size: content.length,
semanticHash,
author: metadata?.author,
message: metadata?.message,
parentVersion
})
// Prune old versions if needed
await this.pruneVersions(path)
return versionId
}
/**
* Get all versions for a file
*/
async getVersions(path: string): Promise<Version[]> {
// Check cache first
if (this.versionCache.has(path)) {
return this.versionCache.get(path)!
}
// Query using EntityManager
const versions = await this.findEntities<Version>(
{ path },
NounType.State,
this.config.maxVersions * 2 // Get extra in case some are pruned
)
// Sort by timestamp (newest first)
versions.sort((a, b) => b.timestamp - a.timestamp)
// Update cache
this.versionCache.set(path, versions)
return versions
}
/**
* Get a specific version's content
*/
async getVersion(path: string, versionId: string): Promise<Buffer | null> {
// Get the version entity
const version = await this.getEntity<Version>(versionId)
if (!version || version.path !== path) {
return null
}
// Get the content from Brainy using the Brainy ID
const brainyId = await this.getBrainyId(versionId)
if (!brainyId) {
return null
}
const entity = await this.brain.get(brainyId)
return entity?.data as Buffer | null
}
/**
* Restore a file to a specific version
*/
async restoreVersion(path: string, versionId: string): Promise<Buffer | null> {
const content = await this.getVersion(path, versionId)
if (!content) {
throw new Error(`Version ${versionId} not found for ${path}`)
}
// Create a new version pointing to the restored one
await this.createVersion(path, content, {
message: `Restored to version ${versionId}`
})
return content
}
/**
* Get version history with diffs
*/
async getVersionHistory(path: string, limit = 10): Promise<Array<{
version: Version
changes?: {
additions: number
deletions: number
semanticChange: number
}
}>> {
const versions = await this.getVersions(path)
const history = []
for (let i = 0; i < Math.min(versions.length, limit); i++) {
const version = versions[i]
let changes = undefined
// Calculate changes from parent
if (version.parentVersion && i < versions.length - 1) {
const parentVersion = versions[i + 1]
if (parentVersion.id === version.parentVersion) {
// Simple size-based diff for now
changes = {
additions: Math.max(0, version.size - parentVersion.size),
deletions: Math.max(0, parentVersion.size - version.size),
semanticChange: version.semanticHash && parentVersion.semanticHash
? this.estimateSemanticChange(version.semanticHash, parentVersion.semanticHash)
: 0
}
}
}
history.push({ version, changes })
}
return history
}
/**
* Prune old versions beyond the limit
*/
private async pruneVersions(path: string): Promise<void> {
const versions = await this.getVersions(path)
if (versions.length <= this.config.maxVersions) {
return
}
// Keep important versions (first, last, and evenly distributed)
const toKeep = new Set<string>()
const toDelete: string[] = []
// Always keep first and last
toKeep.add(versions[0].id) // Newest
toKeep.add(versions[versions.length - 1].id) // Oldest
// Keep evenly distributed versions
const step = Math.floor(versions.length / this.config.maxVersions)
for (let i = 0; i < versions.length; i += step) {
toKeep.add(versions[i].id)
}
// Mark others for deletion
for (const version of versions) {
if (!toKeep.has(version.id)) {
toDelete.push(version.id)
}
}
// Delete excess versions
for (const id of toDelete.slice(0, versions.length - this.config.maxVersions)) {
await this.deleteEntity(id)
}
// Update cache
this.versionCache.set(
path,
versions.filter(v => !toDelete.includes(v.id))
)
}
/**
* Calculate semantic distance between two pieces of content
*/
private async calculateSemanticDistance(oldContent: Buffer, newContent: Buffer): Promise<number> {
// Generate embeddings
const [oldEmbedding, newEmbedding] = await Promise.all([
this.generateEmbedding(oldContent),
this.generateEmbedding(newContent)
])
if (!oldEmbedding || !newEmbedding) {
throw new Error('Failed to generate embeddings')
}
// Calculate cosine distance
return cosineDistance(oldEmbedding, newEmbedding)
}
/**
* Generate embedding for content
*/
private async generateEmbedding(content: Buffer): Promise<number[] | undefined> {
try {
// For text content, use first 10KB for embedding
const text = content.toString('utf8', 0, Math.min(10240, content.length))
// Use Brainy's embedding function
const vector = await this.brain.embed(text)
return vector
} catch (error) {
console.error('Failed to generate embedding:', error)
return undefined
}
}
/**
* Hash content for quick comparison
*/
private hashContent(content: Buffer): string {
return createHash('sha256').update(content).digest('hex')
}
/**
* Hash embedding for quick comparison
*/
private hashEmbedding(embedding: number[]): string {
return createHash('sha256')
.update(Buffer.from(new Float32Array(embedding).buffer))
.digest('hex')
}
/**
* Estimate semantic change from hashes (rough approximation)
*/
private estimateSemanticChange(hash1: string, hash2: string): number {
if (hash1 === hash2) return 0
// Simple hamming distance on first few characters
// This is a rough approximation
let distance = 0
for (let i = 0; i < Math.min(hash1.length, hash2.length, 8); i++) {
if (hash1[i] !== hash2[i]) distance++
}
return distance / 8
}
/**
* Clear version cache for a file
*/
clearCache(path?: string): void {
if (path) {
this.versionCache.delete(path)
} else {
this.versionCache.clear()
}
}
}

View file

@ -12,7 +12,17 @@ import { Brainy } from '../brainy.js'
import { Entity, AddParams, RelateParams, FindParams, Relation } from '../types/brainy.types.js'
import { NounType, VerbType } from '../types/graphTypes.js'
import { PathResolver } from './PathResolver.js'
// Knowledge Layer imports removed - now in KnowledgeAugmentation
import {
SemanticPathResolver,
ProjectionRegistry,
ConceptProjection,
AuthorProjection,
TemporalProjection,
RelationshipProjection,
SimilarityProjection,
TagProjection
} from './semantic/index.js'
// Knowledge Layer can remain as optional augmentation for now
import {
IVirtualFileSystem,
VFSConfig,
@ -48,13 +58,14 @@ import {
*/
export class VirtualFileSystem implements IVirtualFileSystem {
private brain: Brainy
private pathResolver!: PathResolver
private pathResolver!: SemanticPathResolver
private projectionRegistry!: ProjectionRegistry
private config: Required<Omit<VFSConfig, 'rootEntityId'>> & { rootEntityId?: string }
private rootEntityId?: string
private initialized = false
private currentUser: string = 'system' // Track current user for collaboration
// Knowledge Layer removed from core - now optional augmentation
// Knowledge Layer features available via augmentation (brain.use('knowledge'))
// Caches for performance
private contentCache: Map<string, { data: Buffer, timestamp: number }>
@ -93,12 +104,17 @@ export class VirtualFileSystem implements IVirtualFileSystem {
// Create or find root entity
this.rootEntityId = await this.initializeRoot()
// Initialize path resolver
this.pathResolver = new PathResolver(this.brain, this.rootEntityId, {
maxCacheSize: this.config.cache?.maxPaths,
cacheTTL: this.config.cache?.ttl,
hotPathThreshold: 10
})
// Initialize projection registry with auto-discovery of built-in projections
this.projectionRegistry = new ProjectionRegistry()
this.registerBuiltInProjections()
// Initialize semantic path resolver (zero-config, uses brain.config)
this.pathResolver = new SemanticPathResolver(
this.brain,
this, // Pass VFS instance for resolvePath
this.rootEntityId,
this.projectionRegistry
)
// Knowledge Layer is now a separate augmentation
// Enable with: brain.use('knowledge')
@ -112,6 +128,32 @@ export class VirtualFileSystem implements IVirtualFileSystem {
/**
* Create or find the root directory entity
*/
/**
* Auto-register built-in projection strategies
* Zero-config: All semantic dimensions work out of the box
*/
private registerBuiltInProjections(): void {
const projections = [
ConceptProjection,
AuthorProjection,
TemporalProjection,
RelationshipProjection,
SimilarityProjection,
TagProjection
]
for (const ProjectionClass of projections) {
try {
this.projectionRegistry.register(new ProjectionClass())
} catch (err) {
// Silently skip if already registered (e.g., in tests)
if (!(err instanceof Error && err.message.includes('already registered'))) {
throw err
}
}
}
}
private async initializeRoot(): Promise<string> {
// Check if root already exists - search using where clause
const existing = await this.brain.find({
@ -1230,6 +1272,17 @@ export class VirtualFileSystem implements IVirtualFileSystem {
metadata.lineCount = text.split('\n').length
metadata.wordCount = text.split(/\s+/).filter(w => w).length
metadata.charset = 'utf-8'
// Extract concepts using brain.extractConcepts() (neural extraction)
if (this.config.intelligence?.autoConcepts) {
try {
const concepts = await this.brain.extractConcepts(text, { limit: 20 })
metadata.conceptNames = concepts // Flattened for O(log n) queries
} catch (error) {
// Concept extraction is optional - don't fail if it errors
console.debug('Concept extraction failed:', error)
}
}
}
// Extract hash for integrity
@ -1409,9 +1462,6 @@ export class VirtualFileSystem implements IVirtualFileSystem {
maxFileSize: 1_000_000_000, // 1GB
maxPathLength: 4096,
maxDirectoryEntries: 100_000
},
knowledgeLayer: {
enabled: false // Default to disabled
}
}
}
@ -2057,17 +2107,6 @@ export class VirtualFileSystem implements IVirtualFileSystem {
this.invalidateCaches(path)
}
// ============= Knowledge Layer =============
// Knowledge Layer methods are added by KnowledgeLayer.enable()
// This keeps VFS pure and fast while allowing optional intelligence
/**
* Enable Knowledge Layer on this VFS instance
*/
async enableKnowledgeLayer(): Promise<void> {
const { enableKnowledgeLayer } = await import('./KnowledgeLayer.js')
await enableKnowledgeLayer(this, this.brain)
}
/**
* Set the current user for tracking who makes changes

View file

@ -20,12 +20,5 @@ export { DirectoryImporter } from './importers/DirectoryImporter.js'
export { VFSReadStream } from './streams/VFSReadStream.js'
export { VFSWriteStream } from './streams/VFSWriteStream.js'
// Knowledge Layer Components (optional via augmentation)
export { EventRecorder } from './EventRecorder.js'
export { SemanticVersioning } from './SemanticVersioning.js'
export { PersistentEntitySystem } from './PersistentEntitySystem.js'
export { ConceptSystem } from './ConceptSystem.js'
export { GitBridge } from './GitBridge.js'
// Convenience alias
export { VirtualFileSystem as VFS } from './VirtualFileSystem.js'

View file

@ -0,0 +1,147 @@
/**
* Projection Registry
*
* Central registry for all projection strategies
* Manages strategy lookup and execution
*/
import { ProjectionStrategy } from './ProjectionStrategy.js'
import { Brainy } from '../../brainy.js'
import { VirtualFileSystem } from '../VirtualFileSystem.js'
import { VFSEntity } from '../types.js'
/**
* Registry for projection strategies
* Allows dynamic registration and lookup of strategies
*/
export class ProjectionRegistry {
private strategies = new Map<string, ProjectionStrategy>()
/**
* Register a projection strategy
* @param strategy - The strategy to register
* @throws Error if strategy with same name already registered
*/
register(strategy: ProjectionStrategy): void {
if (this.strategies.has(strategy.name)) {
throw new Error(`Projection strategy '${strategy.name}' is already registered`)
}
this.strategies.set(strategy.name, strategy)
}
/**
* Get a projection strategy by name
* @param name - Strategy name
* @returns The strategy or undefined if not found
*/
get(name: string): ProjectionStrategy | undefined {
return this.strategies.get(name)
}
/**
* Check if a strategy is registered
* @param name - Strategy name
*/
has(name: string): boolean {
return this.strategies.has(name)
}
/**
* List all registered strategy names
*/
listDimensions(): string[] {
return Array.from(this.strategies.keys())
}
/**
* Get count of registered strategies
*/
count(): number {
return this.strategies.size
}
/**
* Resolve a dimension value to entity IDs
* Convenience method that looks up strategy and calls resolve()
*
* @param dimension - The semantic dimension
* @param value - The value to resolve
* @param brain - REAL Brainy instance
* @param vfs - REAL VirtualFileSystem instance
* @returns Array of entity IDs
* @throws Error if dimension not registered
*/
async resolve(
dimension: string,
value: any,
brain: Brainy,
vfs: VirtualFileSystem
): Promise<string[]> {
const strategy = this.get(dimension)
if (!strategy) {
throw new Error(`Unknown projection dimension: ${dimension}. Registered dimensions: ${this.listDimensions().join(', ')}`)
}
// Call REAL strategy resolve method
return await strategy.resolve(brain, vfs, value)
}
/**
* List entities in a dimension
* Convenience method for strategies that support listing
*
* @param dimension - The semantic dimension
* @param brain - REAL Brainy instance
* @param vfs - REAL VirtualFileSystem instance
* @param limit - Max results
* @returns Array of VFSEntity
* @throws Error if dimension not registered or doesn't support listing
*/
async list(
dimension: string,
brain: Brainy,
vfs: VirtualFileSystem,
limit?: number
): Promise<VFSEntity[]> {
const strategy = this.get(dimension)
if (!strategy) {
throw new Error(`Unknown projection dimension: ${dimension}`)
}
if (!strategy.list) {
throw new Error(`Projection '${dimension}' does not support listing`)
}
return await strategy.list(brain, vfs, limit)
}
/**
* Unregister a strategy
* Useful for testing or dynamic strategy management
*
* @param name - Strategy name to remove
* @returns true if removed, false if not found
*/
unregister(name: string): boolean {
return this.strategies.delete(name)
}
/**
* Clear all registered strategies
* Useful for testing
*/
clear(): void {
this.strategies.clear()
}
/**
* Get all registered strategies
* Returns a copy to prevent external modification
*/
getAll(): ProjectionStrategy[] {
return Array.from(this.strategies.values())
}
}

View file

@ -0,0 +1,93 @@
/**
* Projection Strategy Interface
*
* Defines how to map semantic path dimensions to Brainy queries
* Each strategy uses EXISTING Brainy indexes and methods
*/
import { Brainy } from '../../brainy.js'
import { VirtualFileSystem } from '../VirtualFileSystem.js'
import { FindParams } from '../../types/brainy.types.js'
import { VFSEntity } from '../types.js'
/**
* Strategy for projecting semantic paths into entity queries
* All implementations MUST use real Brainy methods (no stubs!)
*/
export interface ProjectionStrategy {
/**
* Strategy name (used for registration)
*/
readonly name: string
/**
* Convert semantic value to Brainy FindParams
* Uses EXISTING FindParams type from brainy.types.ts
*/
toQuery(value: any, subpath?: string): FindParams
/**
* Resolve semantic value to entity IDs
* Uses REAL Brainy.find() method
*
* @param brain - REAL Brainy instance
* @param vfs - REAL VirtualFileSystem instance
* @param value - The semantic value to resolve
* @returns Array of entity IDs that match
*/
resolve(brain: Brainy, vfs: VirtualFileSystem, value: any): Promise<string[]>
/**
* List all entities in this dimension
* Optional - not all strategies need to implement
*
* @param brain - REAL Brainy instance
* @param vfs - REAL VirtualFileSystem instance
* @param limit - Max results to return
*/
list?(brain: Brainy, vfs: VirtualFileSystem, limit?: number): Promise<VFSEntity[]>
}
/**
* Base class for projection strategies with common utilities
*/
export abstract class BaseProjectionStrategy implements ProjectionStrategy {
abstract readonly name: string
abstract toQuery(value: any, subpath?: string): FindParams
abstract resolve(brain: Brainy, vfs: VirtualFileSystem, value: any): Promise<string[]>
/**
* Convert Brainy Results to entity IDs
* Helper method for subclasses
*/
protected extractIds(results: Array<{ id: string }>): string[] {
return results.map(r => r.id)
}
/**
* Verify that an entity is a file (not directory)
* Uses REAL Brainy.get() method
*/
protected async isFile(brain: Brainy, entityId: string): Promise<boolean> {
const entity = await brain.get(entityId)
return entity?.metadata?.vfsType === 'file'
}
/**
* Filter entity IDs to only include files
* Uses REAL Brainy.get() for each entity
*/
protected async filterFiles(brain: Brainy, entityIds: string[]): Promise<string[]> {
const files: string[] = []
for (const id of entityIds) {
if (await this.isFile(brain, id)) {
files.push(id)
}
}
return files
}
}

View file

@ -0,0 +1,363 @@
/**
* Semantic Path Parser
*
* Parses semantic filesystem paths into structured queries
* PURE LOGIC - No external dependencies, no async operations
*
* Supported path formats:
* - Traditional: /src/auth.ts
* - By Concept: /by-concept/authentication/login.ts
* - By Author: /by-author/alice/file.ts
* - By Time: /as-of/2024-03-15/file.ts
* - By Relationship: /related-to/src/auth.ts/depth-2
* - By Similarity: /similar-to/src/auth.ts/threshold-0.8
* - By Tag: /by-tag/security/file.ts
*/
export type SemanticDimension =
| 'traditional'
| 'concept'
| 'author'
| 'time'
| 'relationship'
| 'similar'
| 'tag'
export interface ParsedSemanticPath {
dimension: SemanticDimension
value: string | Date | RelationshipValue | SimilarityValue
subpath?: string
filters?: Record<string, any>
}
export interface RelationshipValue {
targetPath: string
depth?: number
relationshipTypes?: string[]
}
export interface SimilarityValue {
targetPath: string
threshold?: number
}
/**
* Semantic Path Parser
* Parses various semantic path formats into structured data
*/
export class SemanticPathParser {
// Regex patterns for each dimension
private static readonly PATTERNS = {
concept: /^\/by-concept\/([^\/]+)(?:\/(.+))?$/,
author: /^\/by-author\/([^\/]+)(?:\/(.+))?$/,
time: /^\/as-of\/(\d{4}-\d{2}-\d{2})(?:\/(.+))?$/,
// Relationship: /related-to/<path>/depth-N/types-X,Y/<subpath>
// Must handle paths with slashes, so capture everything before /depth- or /types-
relationship: /^\/related-to\/(.+?)(?:\/depth-(\d+)|\/types-([^\/]+)|\/(.+))*$/,
// Similarity: /similar-to/<path>/threshold-N/<subpath>
similar: /^\/similar-to\/(.+?)(?:\/threshold-([\d.]+)|\/(.+))*$/,
tag: /^\/by-tag\/([^\/]+)(?:\/(.+))?$/
}
/**
* Parse a path into semantic components
* PURE FUNCTION - no external calls, no async
*/
parse(path: string): ParsedSemanticPath {
if (!path || typeof path !== 'string') {
throw new Error('Path must be a non-empty string')
}
// Normalize path
const normalized = this.normalizePath(path)
// Try concept dimension
const conceptMatch = normalized.match(SemanticPathParser.PATTERNS.concept)
if (conceptMatch) {
return {
dimension: 'concept',
value: conceptMatch[1],
subpath: conceptMatch[2]
}
}
// Try author dimension
const authorMatch = normalized.match(SemanticPathParser.PATTERNS.author)
if (authorMatch) {
return {
dimension: 'author',
value: authorMatch[1],
subpath: authorMatch[2]
}
}
// Try time dimension
const timeMatch = normalized.match(SemanticPathParser.PATTERNS.time)
if (timeMatch) {
const dateStr = timeMatch[1]
const date = this.parseDate(dateStr)
return {
dimension: 'time',
value: date,
subpath: timeMatch[2]
}
}
// Try relationship dimension
if (normalized.startsWith('/related-to/')) {
return this.parseRelationshipPath(normalized)
}
// Try similarity dimension
if (normalized.startsWith('/similar-to/')) {
return this.parseSimilarityPath(normalized)
}
// Try tag dimension
const tagMatch = normalized.match(SemanticPathParser.PATTERNS.tag)
if (tagMatch) {
return {
dimension: 'tag',
value: tagMatch[1],
subpath: tagMatch[2]
}
}
// Default to traditional path
return {
dimension: 'traditional',
value: normalized
}
}
/**
* Check if a path is semantic (non-traditional)
*/
isSemanticPath(path: string): boolean {
if (!path || typeof path !== 'string') {
return false
}
const normalized = this.normalizePath(path)
// Check if matches any semantic pattern
return (
normalized.startsWith('/by-concept/') ||
normalized.startsWith('/by-author/') ||
normalized.startsWith('/as-of/') ||
normalized.startsWith('/related-to/') ||
normalized.startsWith('/similar-to/') ||
normalized.startsWith('/by-tag/')
)
}
/**
* Get the dimension type from a path
*/
getDimension(path: string): SemanticDimension {
return this.parse(path).dimension
}
/**
* Normalize a path - remove trailing slashes, collapse multiple slashes
* PURE FUNCTION
*/
private normalizePath(path: string): string {
// Remove trailing slash (except for root)
let normalized = path.replace(/\/+$/, '')
// Collapse multiple slashes
normalized = normalized.replace(/\/+/g, '/')
// Ensure starts with /
if (!normalized.startsWith('/')) {
normalized = '/' + normalized
}
// Special case: empty path becomes /
if (normalized === '') {
normalized = '/'
}
return normalized
}
/**
* Parse date string (YYYY-MM-DD) into Date object
* PURE FUNCTION
*/
private parseDate(dateStr: string): Date {
const parts = dateStr.split('-')
if (parts.length !== 3) {
throw new Error(`Invalid date format: ${dateStr}. Expected YYYY-MM-DD`)
}
const year = parseInt(parts[0], 10)
const month = parseInt(parts[1], 10) - 1 // Months are 0-indexed in JS
const day = parseInt(parts[2], 10)
if (isNaN(year) || isNaN(month) || isNaN(day)) {
throw new Error(`Invalid date format: ${dateStr}. Expected YYYY-MM-DD`)
}
if (year < 1900 || year > 2100) {
throw new Error(`Invalid year: ${year}. Expected 1900-2100`)
}
if (month < 0 || month > 11) {
throw new Error(`Invalid month: ${month + 1}. Expected 1-12`)
}
if (day < 1 || day > 31) {
throw new Error(`Invalid day: ${day}. Expected 1-31`)
}
return new Date(year, month, day)
}
/**
* Validate parsed path structure
*/
validate(parsed: ParsedSemanticPath): boolean {
if (!parsed || typeof parsed !== 'object') {
return false
}
if (!parsed.dimension) {
return false
}
if (parsed.value === undefined || parsed.value === null) {
return false
}
// Dimension-specific validation
switch (parsed.dimension) {
case 'time':
return parsed.value instanceof Date && !isNaN(parsed.value.getTime())
case 'relationship':
const relValue = parsed.value as RelationshipValue
return typeof relValue.targetPath === 'string' && relValue.targetPath.length > 0
case 'similar':
const simValue = parsed.value as SimilarityValue
return typeof simValue.targetPath === 'string' && simValue.targetPath.length > 0
default:
return typeof parsed.value === 'string' && parsed.value.length > 0
}
}
/**
* Parse relationship paths: /related-to/<path>/depth-N/types-X,Y/<subpath>
*/
private parseRelationshipPath(path: string): ParsedSemanticPath {
// Remove /related-to/ prefix
const withoutPrefix = path.substring('/related-to/'.length)
// Split into segments
const segments = withoutPrefix.split('/')
let targetPath = ''
let depth: number | undefined
let types: string[] | undefined
let subpath: string | undefined
let i = 0
// Collect path segments until we hit depth-, types-, or end
while (i < segments.length) {
const segment = segments[i]
if (segment.startsWith('depth-')) {
depth = parseInt(segment.substring('depth-'.length), 10)
i++
continue
}
if (segment.startsWith('types-')) {
types = segment.substring('types-'.length).split(',')
i++
continue
}
// If we've already collected the target path and found depth/types,
// rest is subpath
if (targetPath && (depth !== undefined || types !== undefined)) {
subpath = segments.slice(i).join('/')
break
}
// Add to target path
if (targetPath) {
targetPath += '/' + segment
} else {
targetPath = segment
}
i++
}
const value: RelationshipValue = {
targetPath,
depth,
relationshipTypes: types
}
return {
dimension: 'relationship',
value,
subpath
}
}
/**
* Parse similarity paths: /similar-to/<path>/threshold-N/<subpath>
*/
private parseSimilarityPath(path: string): ParsedSemanticPath {
// Remove /similar-to/ prefix
const withoutPrefix = path.substring('/similar-to/'.length)
// Split into segments
const segments = withoutPrefix.split('/')
let targetPath = ''
let threshold: number | undefined
let subpath: string | undefined
let i = 0
// Collect path segments until we hit threshold- or end
while (i < segments.length) {
const segment = segments[i]
if (segment.startsWith('threshold-')) {
threshold = parseFloat(segment.substring('threshold-'.length))
i++
// Rest is subpath
if (i < segments.length) {
subpath = segments.slice(i).join('/')
}
break
}
// Add to target path
if (targetPath) {
targetPath += '/' + segment
} else {
targetPath = segment
}
i++
}
const value: SimilarityValue = {
targetPath,
threshold
}
return {
dimension: 'similar',
value,
subpath
}
}
}

View file

@ -0,0 +1,326 @@
/**
* Semantic Path Resolver
*
* Unified path resolver that handles BOTH:
* - Traditional hierarchical paths (/src/auth/login.ts)
* - Semantic projection paths (/by-concept/authentication/...)
*
* Uses EXISTING infrastructure:
* - PathResolver for traditional paths
* - ProjectionRegistry for semantic dimensions
* - SemanticPathParser for path type detection
*/
import { Brainy } from '../../brainy.js'
import { VirtualFileSystem } from '../VirtualFileSystem.js'
import { PathResolver } from '../PathResolver.js'
import { VFSEntity, VFSError, VFSErrorCode } from '../types.js'
import { SemanticPathParser, ParsedSemanticPath } from './SemanticPathParser.js'
import { ProjectionRegistry } from './ProjectionRegistry.js'
import { UnifiedCache } from '../../utils/unifiedCache.js'
/**
* Semantic Path Resolver
* Handles both traditional and semantic paths transparently
*
* Uses Brainy's UnifiedCache for optimal memory management and performance
*/
export class SemanticPathResolver {
private brain: Brainy
private vfs: VirtualFileSystem
private pathResolver: PathResolver
private parser: SemanticPathParser
private registry: ProjectionRegistry
private cache: UnifiedCache
constructor(
brain: Brainy,
vfs: VirtualFileSystem,
rootEntityId: string,
registry: ProjectionRegistry
) {
this.brain = brain
this.vfs = vfs
this.registry = registry
this.parser = new SemanticPathParser()
// Use Brainy's UnifiedCache for semantic path caching
// Zero-config: Uses 2GB default from UnifiedCache
this.cache = new UnifiedCache({
enableRequestCoalescing: true,
enableFairnessCheck: true
})
// Create traditional path resolver (uses its own optimized cache with defaults)
this.pathResolver = new PathResolver(brain, rootEntityId)
}
/**
* Resolve a path to entity ID(s)
* Handles BOTH traditional and semantic paths
*
* For traditional paths: Returns single entity ID
* For semantic paths: Returns first matching entity ID
*
* Uses UnifiedCache with request coalescing to prevent stampede
*
* @param path - Path to resolve (traditional or semantic)
* @param options - Resolution options
* @returns Entity ID
*/
async resolve(path: string, options?: {
followSymlinks?: boolean
cache?: boolean
}): Promise<string> {
// Parse the path to determine dimension
const parsed = this.parser.parse(path)
// Handle based on path dimension
if (parsed.dimension === 'traditional') {
// Use existing PathResolver for traditional paths
return await this.pathResolver.resolve(path, options)
}
// Semantic path - use UnifiedCache with request coalescing
const cacheKey = `semantic:${path}`
if (options?.cache === false) {
// Skip cache if requested
const entityIds = await this.resolveSemanticPathInternal(parsed)
if (entityIds.length === 0) {
throw new VFSError(
VFSErrorCode.ENOENT,
`No entities found for semantic path: ${path}`,
path,
'resolve'
)
}
return entityIds[0]
}
// Use UnifiedCache - automatically handles stampede prevention
const entityIds = await this.cache.get(cacheKey, async () => {
return await this.resolveSemanticPathInternal(parsed)
})
if (!entityIds || entityIds.length === 0) {
throw new VFSError(
VFSErrorCode.ENOENT,
`No entities found for semantic path: ${path}`,
path,
'resolve'
)
}
return entityIds[0]
}
/**
* Resolve semantic path to multiple entity IDs
* This is the polymorphic resolution that returns ALL matches
*
* Uses UnifiedCache for performance
*
* @param path - Semantic path
* @param options - Resolution options
* @returns Array of entity IDs
*/
async resolveAll(path: string, options?: {
cache?: boolean
limit?: number
}): Promise<string[]> {
const parsed = this.parser.parse(path)
if (parsed.dimension === 'traditional') {
// Traditional paths resolve to single entity
const id = await this.pathResolver.resolve(path, options)
return [id]
}
// Use cache if enabled
const cacheKey = `semantic:${path}`
if (options?.cache === false) {
return await this.resolveSemanticPathInternal(parsed, options?.limit)
}
// UnifiedCache with automatic stampede prevention
return await this.cache.get(cacheKey, async () => {
return await this.resolveSemanticPathInternal(parsed, options?.limit)
})
}
/**
* Internal semantic path resolution (called by cache)
* Estimates cost and size for UnifiedCache optimization
*/
private async resolveSemanticPathInternal(
parsed: ParsedSemanticPath,
limit?: number
): Promise<string[]> {
// Resolve based on dimension
let entityIds: string[] = []
switch (parsed.dimension) {
case 'concept':
entityIds = await this.registry.resolve('concept', parsed.value, this.brain, this.vfs)
break
case 'author':
entityIds = await this.registry.resolve('author', parsed.value, this.brain, this.vfs)
break
case 'time':
entityIds = await this.registry.resolve('time', parsed.value, this.brain, this.vfs)
break
case 'relationship':
entityIds = await this.registry.resolve('relationship', parsed.value, this.brain, this.vfs)
break
case 'similar':
entityIds = await this.registry.resolve('similar', parsed.value, this.brain, this.vfs)
break
case 'tag':
// Tags use metadata filtering (concept-like)
entityIds = await this.registry.resolve('tag', parsed.value, this.brain, this.vfs)
break
case 'traditional':
// Shouldn't reach here, but handle it gracefully
return []
default:
throw new VFSError(
VFSErrorCode.ENOTDIR, // Use existing error code
`Unsupported semantic path dimension: ${parsed.dimension}`,
'',
'resolve'
)
}
// Apply subpath filter if specified
if (parsed.subpath) {
entityIds = await this.filterBySubpath(entityIds, parsed.subpath)
}
// Apply limit
if (limit && limit > 0) {
entityIds = entityIds.slice(0, limit)
}
// Result will be cached by UnifiedCache.get() automatically
return entityIds
}
/**
* Filter entity IDs by subpath (filename or partial path)
*/
private async filterBySubpath(entityIds: string[], subpath: string): Promise<string[]> {
const filtered: string[] = []
for (const id of entityIds) {
const entity = await this.brain.get(id)
if (!entity) continue
const name = entity.metadata?.name
const path = entity.metadata?.path
// Check if name or path matches subpath
if (name === subpath || path?.endsWith(subpath)) {
filtered.push(id)
}
}
return filtered
}
/**
* Get children of a directory
* Delegates to PathResolver for traditional directories
* For semantic paths, returns entities in that dimension
*/
async getChildren(dirIdOrPath: string): Promise<VFSEntity[]> {
// If it looks like a path, parse it
if (dirIdOrPath.startsWith('/')) {
const parsed = this.parser.parse(dirIdOrPath)
if (parsed.dimension !== 'traditional') {
// For semantic paths, list entities in that dimension
return await this.listSemanticDimension(parsed)
}
}
// Traditional directory - use PathResolver
return await this.pathResolver.getChildren(dirIdOrPath)
}
/**
* List entities in a semantic dimension
*/
private async listSemanticDimension(parsed: ParsedSemanticPath): Promise<VFSEntity[]> {
switch (parsed.dimension) {
case 'concept':
return await this.registry.list('concept', this.brain, this.vfs)
case 'author':
return await this.registry.list('author', this.brain, this.vfs)
case 'time':
return await this.registry.list('time', this.brain, this.vfs)
case 'tag':
return await this.registry.list('tag', this.brain, this.vfs)
default:
return []
}
}
/**
* Create a path mapping (cache a path resolution)
* Only applies to traditional paths
*/
async createPath(path: string, entityId: string): Promise<void> {
const parsed = this.parser.parse(path)
if (parsed.dimension === 'traditional') {
await this.pathResolver.createPath(path, entityId)
}
// Semantic paths are not cached via createPath
}
/**
* Invalidate path cache
*/
invalidatePath(path: string, recursive = false): void {
const parsed = this.parser.parse(path)
if (parsed.dimension === 'traditional') {
this.pathResolver.invalidatePath(path, recursive)
} else {
// Invalidate semantic cache via UnifiedCache
const cacheKey = `semantic:${path}`
this.cache.delete(cacheKey)
}
}
/**
* Clear all semantic caches
* Uses UnifiedCache's clear method
*/
invalidateSemanticCache(): void {
this.cache.clear()
}
/**
* Cleanup resources
*/
cleanup(): void {
this.pathResolver.cleanup()
this.cache.clear()
}
}

28
src/vfs/semantic/index.ts Normal file
View file

@ -0,0 +1,28 @@
/**
* Semantic VFS - Index
*
* Central export point for all semantic VFS components
*/
// Core components
export { SemanticPathParser } from './SemanticPathParser.js'
export type {
SemanticDimension,
ParsedSemanticPath,
RelationshipValue,
SimilarityValue
} from './SemanticPathParser.js'
export { ProjectionRegistry } from './ProjectionRegistry.js'
export type { ProjectionStrategy } from './ProjectionStrategy.js'
export { BaseProjectionStrategy } from './ProjectionStrategy.js'
export { SemanticPathResolver } from './SemanticPathResolver.js'
// Built-in projections
export { ConceptProjection } from './projections/ConceptProjection.js'
export { AuthorProjection } from './projections/AuthorProjection.js'
export { TemporalProjection } from './projections/TemporalProjection.js'
export { RelationshipProjection } from './projections/RelationshipProjection.js'
export { SimilarityProjection } from './projections/SimilarityProjection.js'
export { TagProjection } from './projections/TagProjection.js'

View file

@ -0,0 +1,83 @@
/**
* Author Projection Strategy
*
* Maps author-based paths to files owned by that author
* Uses EXISTING MetadataIndexManager for O(log n) queries
*/
import { Brainy } from '../../../brainy.js'
import { VirtualFileSystem } from '../../VirtualFileSystem.js'
import { FindParams } from '../../../types/brainy.types.js'
import { BaseProjectionStrategy } from '../ProjectionStrategy.js'
import { VFSEntity } from '../../types.js'
/**
* Author Projection: /by-author/<authorName>/<subpath>
*
* Uses EXISTING infrastructure:
* - Brainy.find() with metadata filters (REAL)
* - MetadataIndexManager for O(log n) owner queries (REAL)
* - VFSMetadata.owner field (REAL - types.ts line 44)
*/
export class AuthorProjection extends BaseProjectionStrategy {
readonly name = 'author'
/**
* Convert author name to Brainy FindParams
*/
toQuery(authorName: string, subpath?: string): FindParams {
const query: FindParams = {
where: {
vfsType: 'file',
owner: authorName
},
limit: 1000
}
// Filter by filename if subpath specified
if (subpath) {
query.where = {
...query.where,
anyOf: [ // BFO logical operator (not $or)
{ name: subpath },
{ path: { endsWith: subpath } } // BFO operator (not $regex)
]
}
}
return query
}
/**
* Resolve author to entity IDs using REAL Brainy.find()
*/
async resolve(brain: Brainy, vfs: VirtualFileSystem, authorName: string): Promise<string[]> {
// Use REAL Brainy metadata filtering
const results = await brain.find({
where: {
vfsType: 'file',
owner: authorName
},
limit: 1000
})
return this.extractIds(results)
}
/**
* List all unique authors
* Uses aggregation over metadata
*/
async list(brain: Brainy, vfs: VirtualFileSystem, limit = 100): Promise<VFSEntity[]> {
// Get all files with owner metadata
const results = await brain.find({
where: {
vfsType: 'file',
owner: { $exists: true }
},
limit
})
return results.map(r => r.entity as VFSEntity)
}
}

View file

@ -0,0 +1,97 @@
/**
* Concept Projection Strategy
*
* Maps concept-based paths to files containing those concepts
* Uses EXISTING ConceptSystem and MetadataIndexManager
*/
import { Brainy } from '../../../brainy.js'
import { VirtualFileSystem } from '../../VirtualFileSystem.js'
import { FindParams } from '../../../types/brainy.types.js'
import { BaseProjectionStrategy } from '../ProjectionStrategy.js'
import { VFSEntity } from '../../types.js'
/**
* Concept Projection: /by-concept/<conceptName>/<subpath>
*
* Uses EXISTING infrastructure:
* - Brainy.find() with metadata filters (REAL - line 580 in brainy.ts)
* - MetadataIndexManager for O(log n) concept queries (REAL)
* - ConceptSystem for concept extraction (REAL - ConceptSystem.ts)
*/
export class ConceptProjection extends BaseProjectionStrategy {
readonly name = 'concept'
/**
* Convert concept name to Brainy FindParams
* Uses EXISTING FindParams.where for metadata filtering
*
* Now uses flattened conceptNames array for O(log n) performance!
*/
toQuery(conceptName: string, subpath?: string): FindParams {
const query: FindParams = {
where: {
vfsType: 'file',
conceptNames: { contains: conceptName } // O(log n) indexed query
},
limit: 1000
}
// If subpath specified, also filter by filename
if (subpath) {
query.where = {
...query.where,
anyOf: [ // BFO logical operator
{ name: subpath },
{ path: { endsWith: subpath } } // BFO operator
]
}
}
return query
}
/**
* Resolve concept to entity IDs using REAL Brainy.find()
* VERIFIED: brain.find() exists at line 580 in brainy.ts
*
* NOW OPTIMIZED: Uses flattened conceptNames for O(log n) indexed queries!
* No more post-filtering - direct index lookup
*/
async resolve(brain: Brainy, vfs: VirtualFileSystem, conceptName: string): Promise<string[]> {
// Verify brain.find is a function (safety check)
if (typeof brain.find !== 'function') {
throw new Error('VERIFICATION FAILED: brain.find is not a function')
}
// Direct O(log n) query using flattened conceptNames array
// VFS automatically flattens concepts to conceptNames on write
const results = await brain.find({
where: {
vfsType: 'file',
conceptNames: { contains: conceptName } // Indexed array query
},
limit: 1000
})
return this.extractIds(results)
}
/**
* List all files with concept metadata
* Uses REAL Brainy.find() with metadata filter
*/
async list(brain: Brainy, vfs: VirtualFileSystem, limit = 100): Promise<VFSEntity[]> {
const results = await brain.find({
where: {
vfsType: 'file',
conceptNames: { exists: true } // Use flattened field
},
limit
})
// Convert to VFSEntity array
// VERIFIED: Result.entity exists in brainy.types.ts
return results.map(r => r.entity as VFSEntity)
}
}

View file

@ -0,0 +1,136 @@
/**
* Relationship Projection Strategy
*
* Maps relationship-based paths to files connected in the knowledge graph
* Uses EXISTING GraphAdjacencyIndex for O(1) traversal
*/
import { Brainy } from '../../../brainy.js'
import { VirtualFileSystem } from '../../VirtualFileSystem.js'
import { FindParams } from '../../../types/brainy.types.js'
import { VerbType } from '../../../types/graphTypes.js'
import { BaseProjectionStrategy } from '../ProjectionStrategy.js'
import { RelationshipValue } from '../SemanticPathParser.js'
/**
* Relationship Projection: /related-to/<path>/depth-N/types-X,Y
*
* Uses EXISTING infrastructure:
* - Brainy.getRelations() for graph traversal (REAL - line 803 in brainy.ts)
* - GraphAdjacencyIndex for O(1) neighbor lookups (REAL)
* - VerbType enum for relationship types (REAL - graphTypes.ts)
*/
export class RelationshipProjection extends BaseProjectionStrategy {
readonly name = 'relationship'
/**
* Convert relationship value to Brainy FindParams
* Note: Graph queries don't use FindParams, but we provide this for consistency
*/
toQuery(value: RelationshipValue, subpath?: string): FindParams {
// This is informational - actual resolution uses getRelations()
return {
where: {
vfsType: 'file'
},
connected: {
to: value.targetPath,
depth: value.depth || 1
},
limit: 1000
}
}
/**
* Resolve relationships using REAL Brainy.getRelations()
* Uses GraphAdjacencyIndex for O(1) graph traversal
*/
async resolve(brain: Brainy, vfs: VirtualFileSystem, value: RelationshipValue): Promise<string[]> {
// Step 1: Resolve target path to entity ID
const targetId = await this.resolvePathToId(vfs, value.targetPath)
if (!targetId) {
return []
}
// Step 2: Get relationships using REAL Brainy graph
const depth = value.depth || 1
const visited = new Set<string>()
const results: string[] = []
await this.traverseRelationships(
brain,
targetId,
depth,
visited,
results,
value.relationshipTypes
)
// Filter to only files
return await this.filterFiles(brain, results)
}
/**
* Recursive graph traversal using REAL Brainy.getRelations()
*/
private async traverseRelationships(
brain: Brainy,
entityId: string,
remainingDepth: number,
visited: Set<string>,
results: string[],
types?: string[]
): Promise<void> {
if (remainingDepth <= 0 || visited.has(entityId)) {
return
}
visited.add(entityId)
// Get outgoing relationships (REAL method - line 803 in brainy.ts)
const relations = await brain.getRelations({
from: entityId,
limit: 100
})
for (const relation of relations) {
// Filter by relationship type if specified
if (types && types.length > 0) {
const relationshipName = relation.type?.toLowerCase()
if (!types.some(t => t.toLowerCase() === relationshipName)) {
continue
}
}
// Add to results
if (!results.includes(relation.to)) {
results.push(relation.to)
}
// Recurse if depth remaining
if (remainingDepth > 1) {
await this.traverseRelationships(
brain,
relation.to,
remainingDepth - 1,
visited,
results,
types
)
}
}
}
/**
* Resolve path to entity ID
* Helper to convert traditional path to entity ID
*/
private async resolvePathToId(vfs: VirtualFileSystem, path: string): Promise<string | null> {
try {
// Use REAL VFS public method
return await vfs.resolvePath(path)
} catch {
return null
}
}
}

View file

@ -0,0 +1,84 @@
/**
* Similarity Projection Strategy
*
* Maps similarity-based paths to files with similar content
* Uses EXISTING HNSW Index for O(log n) vector similarity
*/
import { Brainy } from '../../../brainy.js'
import { VirtualFileSystem } from '../../VirtualFileSystem.js'
import { FindParams } from '../../../types/brainy.types.js'
import { BaseProjectionStrategy } from '../ProjectionStrategy.js'
import { SimilarityValue } from '../SemanticPathParser.js'
/**
* Similarity Projection: /similar-to/<path>/threshold-N
*
* Uses EXISTING infrastructure:
* - Brainy.similar() for vector similarity (REAL - line 680 in brainy.ts)
* - HNSW Index for O(log n) nearest neighbor search (REAL)
* - Cosine similarity for scoring (REAL)
*/
export class SimilarityProjection extends BaseProjectionStrategy {
readonly name = 'similar'
/**
* Convert similarity value to Brainy FindParams
* Note: Similarity uses brain.similar(), not find(), but we provide this for consistency
*/
toQuery(value: SimilarityValue, subpath?: string): FindParams {
// This is informational - actual resolution uses brain.similar()
return {
where: {
vfsType: 'file'
},
near: {
id: value.targetPath,
threshold: value.threshold || 0.7
},
limit: 50
}
}
/**
* Resolve similarity using REAL Brainy.similar()
* Uses HNSW Index for O(log n) vector search
*/
async resolve(brain: Brainy, vfs: VirtualFileSystem, value: SimilarityValue): Promise<string[]> {
// Step 1: Resolve target path to entity ID
const targetId = await this.resolvePathToId(vfs, value.targetPath)
if (!targetId) {
return []
}
// Step 2: Get target entity to use its vector
const targetEntity = await brain.get(targetId)
if (!targetEntity) {
return []
}
// Step 3: Find similar entities using REAL HNSW search
// VERIFIED: brain.similar() exists at line 680 in brainy.ts
const results = await brain.similar({
to: targetEntity,
threshold: value.threshold || 0.7,
limit: 50,
where: { vfsType: 'file' } // Only files
})
// Extract IDs
return this.extractIds(results)
}
/**
* Resolve path to entity ID
*/
private async resolvePathToId(vfs: VirtualFileSystem, path: string): Promise<string | null> {
try {
// Use REAL VFS public method
return await vfs.resolvePath(path)
} catch {
return null
}
}
}

View file

@ -0,0 +1,82 @@
/**
* Tag Projection Strategy
*
* Maps tag-based paths to files with those tags
* Uses EXISTING MetadataIndexManager for O(log n) queries
*/
import { Brainy } from '../../../brainy.js'
import { VirtualFileSystem } from '../../VirtualFileSystem.js'
import { FindParams } from '../../../types/brainy.types.js'
import { BaseProjectionStrategy } from '../ProjectionStrategy.js'
import { VFSEntity } from '../../types.js'
/**
* Tag Projection: /by-tag/<tagName>/<subpath>
*
* Uses EXISTING infrastructure:
* - Brainy.find() with metadata filters (REAL)
* - MetadataIndexManager for O(log n) tag queries (REAL)
* - VFSMetadata.tags field (REAL - types.ts line 66)
*/
export class TagProjection extends BaseProjectionStrategy {
readonly name = 'tag'
/**
* Convert tag name to Brainy FindParams
*/
toQuery(tagName: string, subpath?: string): FindParams {
const query: FindParams = {
where: {
vfsType: 'file',
tags: { contains: tagName } // BFO operator for array contains
},
limit: 1000
}
// Filter by filename if subpath specified
if (subpath) {
query.where = {
...query.where,
anyOf: [ // BFO logical operator
{ name: subpath },
{ path: { endsWith: subpath } } // BFO operator
]
}
}
return query
}
/**
* Resolve tag to entity IDs using REAL Brainy.find()
*/
async resolve(brain: Brainy, vfs: VirtualFileSystem, tagName: string): Promise<string[]> {
// Use REAL Brainy metadata filtering
const results = await brain.find({
where: {
vfsType: 'file',
tags: { contains: tagName } // BFO operator
},
limit: 1000
})
return this.extractIds(results)
}
/**
* List all files with tags
*/
async list(brain: Brainy, vfs: VirtualFileSystem, limit = 100): Promise<VFSEntity[]> {
// Get all files that have tags
const results = await brain.find({
where: {
vfsType: 'file',
tags: { exists: true } // BFO operator
},
limit
})
return results.map(r => r.entity as VFSEntity)
}
}

View file

@ -0,0 +1,103 @@
/**
* Temporal Projection Strategy
*
* Maps time-based paths to files modified at that time
* Uses EXISTING MetadataIndexManager with range queries
*/
import { Brainy } from '../../../brainy.js'
import { VirtualFileSystem } from '../../VirtualFileSystem.js'
import { FindParams } from '../../../types/brainy.types.js'
import { BaseProjectionStrategy } from '../ProjectionStrategy.js'
import { VFSEntity } from '../../types.js'
/**
* Temporal Projection: /as-of/<YYYY-MM-DD>/<subpath>
*
* Uses EXISTING infrastructure:
* - Brainy.find() with range queries (REAL)
* - MetadataIndexManager.$gte/$lte operators (REAL)
* - VFSMetadata.modified field (REAL - types.ts line 49)
*/
export class TemporalProjection extends BaseProjectionStrategy {
readonly name = 'time'
/**
* Convert date to Brainy FindParams with range query
*/
toQuery(date: Date, subpath?: string): FindParams {
// Get start and end of day (24-hour window)
const startOfDay = new Date(date)
startOfDay.setHours(0, 0, 0, 0)
const endOfDay = new Date(date)
endOfDay.setHours(23, 59, 59, 999)
const query: FindParams = {
where: {
vfsType: 'file',
modified: {
greaterEqual: startOfDay.getTime(), // BFO operator
lessEqual: endOfDay.getTime() // BFO operator
}
},
limit: 1000
}
// Filter by filename if subpath specified
if (subpath) {
query.where = {
...query.where,
anyOf: [ // BFO logical operator (not $or)
{ name: subpath },
{ path: { endsWith: subpath } } // BFO operator (not $regex)
]
}
}
return query
}
/**
* Resolve date to entity IDs using REAL Brainy.find()
* Uses MetadataIndexManager range queries for O(log n) performance
*/
async resolve(brain: Brainy, vfs: VirtualFileSystem, date: Date): Promise<string[]> {
const startOfDay = new Date(date)
startOfDay.setHours(0, 0, 0, 0)
const endOfDay = new Date(date)
endOfDay.setHours(23, 59, 59, 999)
// Use REAL Brainy metadata filtering with range operators
const results = await brain.find({
where: {
vfsType: 'file',
modified: {
greaterEqual: startOfDay.getTime(), // BFO operator
lessEqual: endOfDay.getTime() // BFO operator
}
},
limit: 1000
})
return this.extractIds(results)
}
/**
* List recently modified files
*/
async list(brain: Brainy, vfs: VirtualFileSystem, limit = 100): Promise<VFSEntity[]> {
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000)
const results = await brain.find({
where: {
vfsType: 'file',
modified: { greaterEqual: oneDayAgo } // BFO operator
},
limit
})
return results.map(r => r.entity as VFSEntity)
}
}

View file

@ -66,6 +66,7 @@ export interface VFSMetadata {
name: string
confidence: number
}>
conceptNames?: string[] // Flattened concept names for O(log n) indexed queries
todos?: VFSTodo[]
dependencies?: string[] // For code files - what they import
exports?: string[] // For code files - what they export
@ -343,16 +344,6 @@ export interface VFSConfig {
autoConcepts?: boolean // Auto-detect concepts
}
// Knowledge Layer - Optional revolutionary enhancement!
knowledgeLayer?: {
enabled?: boolean // Enable Knowledge Layer features
eventRecording?: boolean // Track all file operations
semanticVersioning?: boolean // Smart versioning based on meaning
persistentEntities?: boolean // Track evolving entities
concepts?: boolean // Universal concept system
gitBridge?: boolean // Git import/export support
}
// Permissions
permissions?: {
defaultFile?: number // Default file permissions (0o644)