2025-09-24 17:31:48 -07:00
|
|
|
/**
|
|
|
|
|
* Virtual Filesystem Tests
|
|
|
|
|
*
|
|
|
|
|
* REAL tests for production VFS implementation
|
|
|
|
|
* These tests demonstrate that VFS actually works
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
refactor(8.0): remove dead, unreachable, and unwired modules
Pre-GA dead-code sweep. A deterministic import-reachability walk from the
package's real entry points (the exports map, the CLI bin, the conversation
surface) found 34 source modules that nothing reachable imports — they
compile and ship as dist output that no consumer or internal path can ever
reach. All were superseded duplicates, abandoned parallel implementations,
or built-but-never-wired features:
- superseded duplicates of live modules: an older "unified" entry, a
standalone neural-import variant, a static NLP processor + its matcher,
a parallel API-types module, a duplicate progress-types module
- an abandoned import path (orchestrator + entity deduplicator + barrels)
left behind when ingestion moved to the coordinator
- unwired feature modules (instance pool, import presets, cached
embeddings, relationship-confidence scorer) reachable only from tests
- dead leaf utilities (write buffer, deleted-items index, bounded
registry, two crypto shims, a cache manager, a structured logger, a
stale v5 type-migration helper, a browser-only FS type shim) and
several dead re-export barrels
- a CLI catalog command wired into no command
Also removed two tests that only exercised deleted modules, repointed two
VFS tests off a deleted barrel onto the implementation module, and deleted
one example that demoed removed features.
Verified: clean build (both tsc passes), unit 1505 + integration 607 green,
and a re-run of the reachability walk reports zero remaining orphans.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 15:18:40 -07:00
|
|
|
import { VirtualFileSystem } from '../../src/vfs/VirtualFileSystem.js'
|
2025-09-24 17:31:48 -07:00
|
|
|
import { Brainy } from '../../src/brainy.js'
|
|
|
|
|
import { VFSErrorCode } from '../../src/vfs/types.js'
|
|
|
|
|
|
|
|
|
|
describe('VirtualFileSystem - Production Tests', () => {
|
|
|
|
|
let vfs: VirtualFileSystem
|
|
|
|
|
let brain: Brainy
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
// Create a fresh Brainy instance with in-memory storage for each test
|
feat(8.0)!: flip requireSubtype default to true (BRAINY-8.0-SUBTYPE-CONTRACT § C-1)
Brainy 8.0 makes subtype required by default on every public write path
(`add`, `addMany`, `update`, `relate`, `relateMany`, `updateRelation`,
import). Per the locked C-1 contract, every entity and relation gets a
non-empty subtype string by the time the storage layer sees it.
OPT-OUT REMAINS FULLY SUPPORTED
The runtime flag is still consumer-controlled. Three opt-out paths
cover migration / legacy fixtures / typed escape:
- `new Brainy({ requireSubtype: false })` — last-resort: turn off the
contract entirely. Recommended only for migration windows or test
fixtures that legitimately can't supply a subtype.
- `new Brainy({ requireSubtype: { except: [NounType.Thing, ...] } })` —
per-type allowlist: strict everywhere except the listed types.
- `brain.requireSubtype(type, options)` — per-type registration with
optional vocabulary. Composes with the brain-wide flag.
Default is now `true`. Opt-out is explicit and documented; nothing
silently degrades.
TEST SWEEP
Bulk-applied `requireSubtype: false` to every `new Brainy({...})` call
site across 120 test files. Three sed patterns covered the shapes:
- `new Brainy({` → `new Brainy({ requireSubtype: false,`
- `new Brainy<T>({` → `new Brainy<T>({ requireSubtype: false,`
- `new Brainy()` → `new Brainy({ requireSubtype: false })`
tests/helpers/test-factory.ts → createTestConfig() defaults
`requireSubtype: false` so test files using the helper inherit the
opt-out without per-site edits.
The test sites that DO exercise subtype semantics (the
subtype-and-facets suite, the strict-mode-self-test suite, the verb-
subtype-and-enforcement suite, etc.) already pass real subtypes — they
were the 7.30.x acceptance tests for this contract. Those tests
continue to pass unchanged.
CHANGES
src/brainy.ts
- normalizeConfig() — `requireSubtype` default `false` → `true`.
Comment refreshed to document the three opt-out paths.
tests/* (120 files)
- Bulk-edited brain construction sites. No functional test changes; the
opt-out preserves the test author's original intent.
tests/helpers/test-factory.ts
- createTestConfig() base config gains `requireSubtype: false`.
NO-OP for consumers who were already passing subtype on every write.
For consumers who weren't, the upgrade path is one of the three opt-out
forms above. Migration recipe documented in 8.0 release notes (next
commit).
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1408 / 1409 (same pre-existing race-condition outstanding;
no other regressions from the flip)
2026-06-09 14:58:25 -07:00
|
|
|
brain = new Brainy({ requireSubtype: false,
|
2025-09-24 17:31:48 -07:00
|
|
|
storage: { type: 'memory' },
|
|
|
|
|
silent: true // Reduce test output
|
|
|
|
|
})
|
|
|
|
|
await brain.init()
|
|
|
|
|
|
|
|
|
|
// Create VFS on top of Brainy
|
2025-11-02 11:38:12 -08:00
|
|
|
vfs = brain.vfs
|
2025-09-24 17:31:48 -07:00
|
|
|
await vfs.init()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
if (vfs) {
|
|
|
|
|
await vfs.close()
|
|
|
|
|
}
|
|
|
|
|
if (brain) {
|
|
|
|
|
await brain.close()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Core File Operations', () => {
|
|
|
|
|
it('should write and read a file', async () => {
|
|
|
|
|
const content = 'Hello, VFS World!'
|
|
|
|
|
const path = '/test.txt'
|
|
|
|
|
|
|
|
|
|
// Write file
|
|
|
|
|
await vfs.writeFile(path, content)
|
|
|
|
|
|
|
|
|
|
// Read file
|
|
|
|
|
const result = await vfs.readFile(path)
|
|
|
|
|
expect(result.toString()).toBe(content)
|
|
|
|
|
|
|
|
|
|
// Verify file exists
|
|
|
|
|
const exists = await vfs.exists(path)
|
|
|
|
|
expect(exists).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle binary files', async () => {
|
|
|
|
|
const binaryData = Buffer.from([0x00, 0x01, 0x02, 0xFF])
|
|
|
|
|
const path = '/binary.dat'
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, binaryData)
|
|
|
|
|
const result = await vfs.readFile(path)
|
|
|
|
|
|
|
|
|
|
expect(Buffer.compare(result, binaryData)).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should update existing files', async () => {
|
|
|
|
|
const path = '/update-test.txt'
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, 'original')
|
|
|
|
|
await vfs.writeFile(path, 'updated')
|
|
|
|
|
|
|
|
|
|
const content = await vfs.readFile(path)
|
|
|
|
|
expect(content.toString()).toBe('updated')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should append to files', async () => {
|
|
|
|
|
const path = '/append-test.txt'
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, 'Hello')
|
|
|
|
|
await vfs.appendFile(path, ' World')
|
|
|
|
|
|
|
|
|
|
const content = await vfs.readFile(path)
|
|
|
|
|
expect(content.toString()).toBe('Hello World')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should delete files', async () => {
|
|
|
|
|
const path = '/delete-me.txt'
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, 'temporary')
|
|
|
|
|
expect(await vfs.exists(path)).toBe(true)
|
|
|
|
|
|
|
|
|
|
await vfs.unlink(path)
|
|
|
|
|
expect(await vfs.exists(path)).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle file metadata', async () => {
|
|
|
|
|
const path = '/metadata-test.json'
|
|
|
|
|
const content = JSON.stringify({ key: 'value' })
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(path, content)
|
|
|
|
|
|
|
|
|
|
const stats = await vfs.stat(path)
|
|
|
|
|
expect(stats.isFile()).toBe(true)
|
|
|
|
|
expect(stats.size).toBe(content.length)
|
|
|
|
|
expect(stats.path).toBe(path)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Directory Operations', () => {
|
|
|
|
|
it('should create directories', async () => {
|
|
|
|
|
const path = '/my-directory'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(path)
|
|
|
|
|
|
|
|
|
|
const exists = await vfs.exists(path)
|
|
|
|
|
expect(exists).toBe(true)
|
|
|
|
|
|
|
|
|
|
const stats = await vfs.stat(path)
|
|
|
|
|
expect(stats.isDirectory()).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should create nested directories recursively', async () => {
|
|
|
|
|
const path = '/level1/level2/level3'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(path, { recursive: true })
|
|
|
|
|
|
|
|
|
|
expect(await vfs.exists('/level1')).toBe(true)
|
|
|
|
|
expect(await vfs.exists('/level1/level2')).toBe(true)
|
|
|
|
|
expect(await vfs.exists(path)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should list directory contents', async () => {
|
|
|
|
|
const dir = '/list-test'
|
|
|
|
|
await vfs.mkdir(dir)
|
|
|
|
|
|
|
|
|
|
// Create some files
|
|
|
|
|
await vfs.writeFile(`${dir}/file1.txt`, 'content1')
|
|
|
|
|
await vfs.writeFile(`${dir}/file2.txt`, 'content2')
|
|
|
|
|
await vfs.mkdir(`${dir}/subdir`)
|
|
|
|
|
|
|
|
|
|
// List directory
|
|
|
|
|
const contents = await vfs.readdir(dir) as string[]
|
|
|
|
|
|
|
|
|
|
expect(contents).toContain('file1.txt')
|
|
|
|
|
expect(contents).toContain('file2.txt')
|
|
|
|
|
expect(contents).toContain('subdir')
|
|
|
|
|
expect(contents).toHaveLength(3)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should list directory with file types', async () => {
|
|
|
|
|
const dir = '/typed-list'
|
|
|
|
|
await vfs.mkdir(dir)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(`${dir}/doc.txt`, 'text')
|
|
|
|
|
await vfs.mkdir(`${dir}/folder`)
|
|
|
|
|
|
|
|
|
|
const entries = await vfs.readdir(dir, { withFileTypes: true })
|
|
|
|
|
|
|
|
|
|
expect(entries).toHaveLength(2)
|
|
|
|
|
const file = entries.find(e => e.name === 'doc.txt')
|
|
|
|
|
const folder = entries.find(e => e.name === 'folder')
|
|
|
|
|
|
|
|
|
|
expect(file?.type).toBe('file')
|
|
|
|
|
expect(folder?.type).toBe('directory')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should remove empty directories', async () => {
|
|
|
|
|
const path = '/empty-dir'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(path)
|
|
|
|
|
expect(await vfs.exists(path)).toBe(true)
|
|
|
|
|
|
|
|
|
|
await vfs.rmdir(path)
|
|
|
|
|
expect(await vfs.exists(path)).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should recursively remove directories', async () => {
|
|
|
|
|
const dir = '/recursive-delete'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(`${dir}/sub1/sub2`, { recursive: true })
|
|
|
|
|
await vfs.writeFile(`${dir}/file.txt`, 'content')
|
|
|
|
|
await vfs.writeFile(`${dir}/sub1/file2.txt`, 'content2')
|
|
|
|
|
|
|
|
|
|
await vfs.rmdir(dir, { recursive: true })
|
|
|
|
|
|
|
|
|
|
expect(await vfs.exists(dir)).toBe(false)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Path Resolution', () => {
|
|
|
|
|
it('should resolve absolute paths', async () => {
|
|
|
|
|
await vfs.mkdir('/absolute/path', { recursive: true })
|
|
|
|
|
await vfs.writeFile('/absolute/path/file.txt', 'content')
|
|
|
|
|
|
|
|
|
|
const resolved = await vfs.resolvePath('/absolute/path/file.txt')
|
|
|
|
|
expect(resolved).toBe('/absolute/path/file.txt')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should normalize paths with multiple slashes', async () => {
|
|
|
|
|
await vfs.mkdir('/normal', { recursive: true })
|
|
|
|
|
await vfs.writeFile('/normal/file.txt', 'content')
|
|
|
|
|
|
|
|
|
|
// Multiple slashes should work
|
|
|
|
|
const content = await vfs.readFile('//normal///file.txt')
|
|
|
|
|
expect(content.toString()).toBe('content')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle deep nesting', async () => {
|
|
|
|
|
const deepPath = '/a/b/c/d/e/f/g/h/i/j/k/file.txt'
|
|
|
|
|
const dirPath = '/a/b/c/d/e/f/g/h/i/j/k'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(dirPath, { recursive: true })
|
|
|
|
|
await vfs.writeFile(deepPath, 'deep content')
|
|
|
|
|
|
|
|
|
|
const content = await vfs.readFile(deepPath)
|
|
|
|
|
expect(content.toString()).toBe('deep content')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Semantic Search (Triple Intelligence)', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
// Create test files with different content
|
|
|
|
|
await vfs.mkdir('/search-test', { recursive: true })
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile('/search-test/auth.js', `
|
|
|
|
|
function authenticate(username, password) {
|
|
|
|
|
// User authentication logic
|
|
|
|
|
return checkCredentials(username, password)
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile('/search-test/login.html', `
|
|
|
|
|
<form>
|
|
|
|
|
<input type="text" name="username" />
|
|
|
|
|
<input type="password" name="password" />
|
|
|
|
|
<button>Sign In</button>
|
|
|
|
|
</form>
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile('/search-test/readme.md', `
|
|
|
|
|
# Project Documentation
|
|
|
|
|
This project implements a secure authentication system.
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile('/search-test/config.json', `
|
|
|
|
|
{
|
|
|
|
|
"database": "postgres://localhost/myapp",
|
|
|
|
|
"port": 3000
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should search files by semantic meaning', async () => {
|
2025-10-07 11:51:17 -07:00
|
|
|
// Skip in unit test mode (mocked embeddings don't match file content)
|
|
|
|
|
if ((globalThis as any).__BRAINY_UNIT_TEST__) {
|
|
|
|
|
console.log('⏭️ Skipping semantic search test in unit mode')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 17:31:48 -07:00
|
|
|
// Search for authentication-related files
|
|
|
|
|
const results = await vfs.search('user authentication security', {
|
|
|
|
|
path: '/search-test'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Should find auth.js and login.html as most relevant
|
|
|
|
|
expect(results.length).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
|
|
const paths = results.map(r => r.path)
|
|
|
|
|
expect(paths).toContain('/search-test/auth.js')
|
|
|
|
|
expect(paths).toContain('/search-test/login.html')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Extended Attributes', () => {
|
|
|
|
|
it('should store and retrieve custom attributes', async () => {
|
|
|
|
|
const path = '/attributed-file.txt'
|
|
|
|
|
await vfs.writeFile(path, 'content')
|
|
|
|
|
|
|
|
|
|
// Get initial attributes
|
|
|
|
|
const attrs = await vfs.listxattr(path)
|
|
|
|
|
expect(attrs).toEqual([])
|
|
|
|
|
|
|
|
|
|
// Set custom attribute
|
|
|
|
|
await vfs.setxattr(path, 'project', 'alpha')
|
|
|
|
|
await vfs.setxattr(path, 'priority', 'high')
|
|
|
|
|
|
|
|
|
|
// Get specific attribute
|
|
|
|
|
const project = await vfs.getxattr(path, 'project')
|
|
|
|
|
expect(project).toBe('alpha')
|
|
|
|
|
|
|
|
|
|
// List all attributes
|
|
|
|
|
const allAttrs = await vfs.listxattr(path)
|
|
|
|
|
expect(allAttrs).toContain('project')
|
|
|
|
|
expect(allAttrs).toContain('priority')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle todos on files', async () => {
|
|
|
|
|
const path = '/todo-file.js'
|
|
|
|
|
await vfs.writeFile(path, 'function incomplete() {}')
|
|
|
|
|
|
|
|
|
|
// Get initial todos (should be empty)
|
|
|
|
|
const todos = await vfs.getTodos(path)
|
|
|
|
|
expect(todos).toBeUndefined()
|
|
|
|
|
|
|
|
|
|
// Add a todo
|
|
|
|
|
await vfs.addTodo(path, {
|
|
|
|
|
id: '1',
|
|
|
|
|
task: 'Complete implementation',
|
|
|
|
|
priority: 'high',
|
|
|
|
|
status: 'pending'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Verify todo was added
|
|
|
|
|
const updatedTodos = await vfs.getTodos(path)
|
|
|
|
|
expect(updatedTodos).toHaveLength(1)
|
|
|
|
|
expect(updatedTodos![0].task).toBe('Complete implementation')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Error Handling', () => {
|
|
|
|
|
it('should throw ENOENT for non-existent files', async () => {
|
|
|
|
|
await expect(vfs.readFile('/does-not-exist.txt'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('No such file or directory')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should throw EISDIR when reading a directory', async () => {
|
|
|
|
|
await vfs.mkdir('/is-directory')
|
|
|
|
|
|
|
|
|
|
await expect(vfs.readFile('/is-directory'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('Is a directory')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should throw ENOTDIR when listing a file', async () => {
|
|
|
|
|
await vfs.writeFile('/is-file.txt', 'content')
|
|
|
|
|
|
|
|
|
|
await expect(vfs.readdir('/is-file.txt'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('Not a directory')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should throw ENOTEMPTY when removing non-empty directory', async () => {
|
|
|
|
|
await vfs.mkdir('/non-empty')
|
|
|
|
|
await vfs.writeFile('/non-empty/file.txt', 'content')
|
|
|
|
|
|
|
|
|
|
await expect(vfs.rmdir('/non-empty'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('Directory not empty')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should throw EEXIST when creating existing directory', async () => {
|
|
|
|
|
await vfs.mkdir('/already-exists')
|
|
|
|
|
|
|
|
|
|
await expect(vfs.mkdir('/already-exists'))
|
|
|
|
|
.rejects
|
|
|
|
|
.toThrow('Directory exists')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Performance', () => {
|
|
|
|
|
it('should handle many files efficiently', async () => {
|
|
|
|
|
const dir = '/performance-test'
|
|
|
|
|
await vfs.mkdir(dir)
|
|
|
|
|
|
|
|
|
|
const startWrite = Date.now()
|
|
|
|
|
|
|
|
|
|
// Create 100 files
|
|
|
|
|
const promises = []
|
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
|
|
|
promises.push(
|
|
|
|
|
vfs.writeFile(`${dir}/file-${i}.txt`, `Content ${i}`)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
await Promise.all(promises)
|
|
|
|
|
|
|
|
|
|
const writeTime = Date.now() - startWrite
|
|
|
|
|
console.log(`Written 100 files in ${writeTime}ms`)
|
|
|
|
|
|
|
|
|
|
// List directory
|
|
|
|
|
const startList = Date.now()
|
|
|
|
|
const files = await vfs.readdir(dir)
|
|
|
|
|
const listTime = Date.now() - startList
|
|
|
|
|
|
|
|
|
|
expect(files).toHaveLength(100)
|
|
|
|
|
console.log(`Listed 100 files in ${listTime}ms`)
|
|
|
|
|
|
|
|
|
|
// Performance assertions
|
2025-11-05 17:01:44 -08:00
|
|
|
expect(writeTime).toBeLessThan(5500) // v5.4.0: Type-first storage takes slightly longer
|
2025-09-24 17:31:48 -07:00
|
|
|
expect(listTime).toBeLessThan(100) // Should list 100 files in < 100ms
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should cache paths for fast repeated access', async () => {
|
|
|
|
|
const path = '/cached/file.txt'
|
|
|
|
|
await vfs.mkdir('/cached')
|
|
|
|
|
await vfs.writeFile(path, 'cached content')
|
|
|
|
|
|
2026-06-23 16:02:07 -07:00
|
|
|
// Prime the path cache + verify the content round-trips.
|
|
|
|
|
expect((await vfs.readFile(path)).toString()).toBe('cached content')
|
|
|
|
|
|
|
|
|
|
// Cached repeated access is sub-millisecond. Average many reads so the result
|
|
|
|
|
// doesn't hinge on Date.now()'s millisecond rounding of a single sub-ms op — the
|
|
|
|
|
// old cold-vs-warm compare spuriously failed when both rounded to 0/1ms. A
|
|
|
|
|
// generous absolute bound still catches a regression to per-read disk/index work.
|
|
|
|
|
const ITERATIONS = 100
|
|
|
|
|
const start = performance.now()
|
|
|
|
|
for (let i = 0; i < ITERATIONS; i++) await vfs.readFile(path)
|
|
|
|
|
const avgWarmMs = (performance.now() - start) / ITERATIONS
|
|
|
|
|
|
|
|
|
|
expect(avgWarmMs).toBeLessThan(5)
|
2025-09-24 17:31:48 -07:00
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Real-World Scenarios', () => {
|
|
|
|
|
it('should handle a code project structure', async () => {
|
|
|
|
|
// Create a typical project structure
|
|
|
|
|
const project = '/my-project'
|
|
|
|
|
|
|
|
|
|
await vfs.mkdir(`${project}/src/components`, { recursive: true })
|
|
|
|
|
await vfs.mkdir(`${project}/src/utils`, { recursive: true })
|
|
|
|
|
await vfs.mkdir(`${project}/tests`, { recursive: true })
|
|
|
|
|
await vfs.mkdir(`${project}/docs`, { recursive: true })
|
|
|
|
|
|
|
|
|
|
// Add files
|
|
|
|
|
await vfs.writeFile(`${project}/package.json`, JSON.stringify({
|
|
|
|
|
name: 'my-project',
|
|
|
|
|
version: '1.0.0'
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(`${project}/README.md`, '# My Project')
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(`${project}/src/index.js`, `
|
|
|
|
|
import App from './components/App'
|
|
|
|
|
export default App
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
await vfs.writeFile(`${project}/src/components/App.js`, `
|
2025-10-01 13:50:21 -07:00
|
|
|
// React component
|
2025-09-24 17:31:48 -07:00
|
|
|
export default function App() {
|
|
|
|
|
return 'Hello World'
|
|
|
|
|
}
|
|
|
|
|
`)
|
|
|
|
|
|
|
|
|
|
// Verify structure
|
|
|
|
|
const srcFiles = await vfs.readdir(`${project}/src`)
|
|
|
|
|
expect(srcFiles).toContain('components')
|
|
|
|
|
expect(srcFiles).toContain('utils')
|
|
|
|
|
expect(srcFiles).toContain('index.js')
|
|
|
|
|
|
2025-10-07 11:51:17 -07:00
|
|
|
// Skip semantic search in unit test mode
|
|
|
|
|
if ((globalThis as any).__BRAINY_UNIT_TEST__) {
|
|
|
|
|
console.log('⏭️ Skipping semantic search test in unit mode')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 17:31:48 -07:00
|
|
|
// Search for components
|
|
|
|
|
const components = await vfs.search('react component', {
|
|
|
|
|
path: project
|
|
|
|
|
})
|
|
|
|
|
expect(components.length).toBeGreaterThan(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should support file relationships', async () => {
|
|
|
|
|
// Create related files
|
|
|
|
|
await vfs.writeFile('/code/user-model.js', 'class User {}')
|
|
|
|
|
await vfs.writeFile('/tests/user-model.test.js', 'test User')
|
|
|
|
|
await vfs.writeFile('/docs/user-api.md', '# User API')
|
|
|
|
|
|
|
|
|
|
// Add relationships
|
|
|
|
|
await vfs.addRelationship(
|
|
|
|
|
'/tests/user-model.test.js',
|
|
|
|
|
'/code/user-model.js',
|
|
|
|
|
'references' // Test file references the code it tests
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await vfs.addRelationship(
|
|
|
|
|
'/docs/user-api.md',
|
|
|
|
|
'/code/user-model.js',
|
|
|
|
|
'references' // Documentation references the code it documents
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Query relationships
|
|
|
|
|
const relationships = await vfs.getRelationships('/code/user-model.js')
|
|
|
|
|
expect(relationships.length).toBe(2)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('VirtualFileSystem - Watch System', () => {
|
|
|
|
|
let vfs: VirtualFileSystem
|
|
|
|
|
let brain: Brainy
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
feat(8.0)!: flip requireSubtype default to true (BRAINY-8.0-SUBTYPE-CONTRACT § C-1)
Brainy 8.0 makes subtype required by default on every public write path
(`add`, `addMany`, `update`, `relate`, `relateMany`, `updateRelation`,
import). Per the locked C-1 contract, every entity and relation gets a
non-empty subtype string by the time the storage layer sees it.
OPT-OUT REMAINS FULLY SUPPORTED
The runtime flag is still consumer-controlled. Three opt-out paths
cover migration / legacy fixtures / typed escape:
- `new Brainy({ requireSubtype: false })` — last-resort: turn off the
contract entirely. Recommended only for migration windows or test
fixtures that legitimately can't supply a subtype.
- `new Brainy({ requireSubtype: { except: [NounType.Thing, ...] } })` —
per-type allowlist: strict everywhere except the listed types.
- `brain.requireSubtype(type, options)` — per-type registration with
optional vocabulary. Composes with the brain-wide flag.
Default is now `true`. Opt-out is explicit and documented; nothing
silently degrades.
TEST SWEEP
Bulk-applied `requireSubtype: false` to every `new Brainy({...})` call
site across 120 test files. Three sed patterns covered the shapes:
- `new Brainy({` → `new Brainy({ requireSubtype: false,`
- `new Brainy<T>({` → `new Brainy<T>({ requireSubtype: false,`
- `new Brainy()` → `new Brainy({ requireSubtype: false })`
tests/helpers/test-factory.ts → createTestConfig() defaults
`requireSubtype: false` so test files using the helper inherit the
opt-out without per-site edits.
The test sites that DO exercise subtype semantics (the
subtype-and-facets suite, the strict-mode-self-test suite, the verb-
subtype-and-enforcement suite, etc.) already pass real subtypes — they
were the 7.30.x acceptance tests for this contract. Those tests
continue to pass unchanged.
CHANGES
src/brainy.ts
- normalizeConfig() — `requireSubtype` default `false` → `true`.
Comment refreshed to document the three opt-out paths.
tests/* (120 files)
- Bulk-edited brain construction sites. No functional test changes; the
opt-out preserves the test author's original intent.
tests/helpers/test-factory.ts
- createTestConfig() base config gains `requireSubtype: false`.
NO-OP for consumers who were already passing subtype on every write.
For consumers who weren't, the upgrade path is one of the three opt-out
forms above. Migration recipe documented in 8.0 release notes (next
commit).
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1408 / 1409 (same pre-existing race-condition outstanding;
no other regressions from the flip)
2026-06-09 14:58:25 -07:00
|
|
|
brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true })
|
2025-09-24 17:31:48 -07:00
|
|
|
await brain.init()
|
|
|
|
|
vfs = new VirtualFileSystem(brain)
|
|
|
|
|
await vfs.init()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
if (vfs) {
|
|
|
|
|
await vfs.close()
|
|
|
|
|
}
|
|
|
|
|
if (brain) {
|
|
|
|
|
await brain.close()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should watch for file changes', async () => {
|
|
|
|
|
const path = '/watched-file.txt'
|
|
|
|
|
const events: Array<{ type: string, path: string | null }> = []
|
|
|
|
|
|
|
|
|
|
// Set up watcher
|
|
|
|
|
const watcher = vfs.watch(path, (eventType, filename) => {
|
|
|
|
|
events.push({ type: eventType, path: filename })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Trigger events
|
|
|
|
|
await vfs.writeFile(path, 'initial') // Create
|
|
|
|
|
await vfs.writeFile(path, 'updated') // Change
|
|
|
|
|
await vfs.unlink(path) // Delete
|
|
|
|
|
|
|
|
|
|
// Verify events were triggered
|
|
|
|
|
expect(events).toHaveLength(3)
|
|
|
|
|
expect(events[0].type).toBe('rename') // Create is a rename event
|
|
|
|
|
expect(events[1].type).toBe('change')
|
|
|
|
|
expect(events[2].type).toBe('rename') // Delete is a rename event
|
|
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
|
watcher.close()
|
|
|
|
|
})
|
|
|
|
|
})
|