Add YAML frontmatter (slug, public, category, template, order) to 8
existing docs and 2 new getting-started guides (installation, quick-start).
Include docs/**/*.md in npm package files so the portal sync-docs script
can read them from node_modules after publish.
Update CLAUDE.md with docs pipeline trigger phrases and release checklist.
Three bugs caused deleted entities to persist in the metadata index:
1. idMapper never cleaned up — EntityIdMapper accumulated UUID→int mappings
permanently. idMapper.getAllIntIds() is used as the universe for ne and
exists:false operators, so deleted entities returned in those queries
indefinitely. Fix: removeFromIndex() now calls idMapper.remove(id) and
idMapper.flush() after all bitmap operations complete (must be last because
removeFromChunk() reads idMapper.getInt(id) internally).
2. Optional fields indexed as __NULL__ but never unindexed — entityForIndexing
in add() included confidence, weight, and createdBy as explicit keys even
when undefined. Object.entries() preserves undefined-valued keys so
extractIndexableFields() indexed them as '__NULL__' bitmap entries.
storageMetadata omitted those keys via conditional spreading, so
removeFromIndex() passed a structure without those keys and never cleaned
them up. Fix: entityForIndexing now uses conditional spreading for
confidence, weight, and createdBy matching storageMetadata exactly.
3. result.successful updated before transaction commits — deleteMany() pushed
ids to result.successful inside the transaction builder, before
transaction.execute() ran. A rollback would leave result.successful
containing ids that were never actually deleted. Fix: queued ids are held
in a local chunkQueued array and moved to result.successful only after
executeTransaction() resolves without throwing.
Adds regression test suite (14 tests) covering delete() and deleteMany() for
type-index cleanup, ne operator, exists:false operator, optional-field indexing,
and partial deletion correctness.
Reported by wickworks team.
The pkg/ directory containing the Candle WASM binary was excluded from
the npm tarball because it contained its own package.json and .gitignore
(with `*` pattern). The build:copy-wasm script now skips these files
when copying to dist/, ensuring candle_embeddings.js/.wasm are included.
Also adds ./embeddings/wasm export subpath for clean ESM imports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a write-time incremental aggregation engine that maintains running
totals on every add/update/delete for O(1) read performance. Integrates
into brain.find({ aggregate }) for a unified query API.
Core features:
- AggregationIndex with defineAggregate()/removeAggregate() API
- Five aggregation operations: SUM, COUNT, AVG, MIN, MAX
- GROUP BY with multiple dimensions including time windows
- Time window bucketing: hour, day, week, month, quarter, year, custom
- Materialization of results as NounType.Measurement entities
- Debounced persistence of definitions and state to storage
- Definition change detection via FNV-1a hashing with auto-rebuild
- Infinite loop prevention for materialized entities
- 'aggregation' plugin provider key for native acceleration
- Lazy initialization (created on first defineAggregate() call)
- 73 tests (unit + integration) covering all functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor-friendly CLAUDE.md with setup, conventions, and architecture
overview. Comprehensive skills/architecture.md verified against actual
codebase covering all 31 source directories, 38+ exports, and major
subsystems (distributed, transactions, neural, CLI, MCP, COW, etc).
Remove CLAUDE.md from .gitignore since the new version is designed
for public contributors.
- Store data opaquely in add() and update() instead of spreading object
properties into top-level metadata. data is for semantic search (HNSW),
metadata is for structured where-filter queries (MetadataIndex).
- Fix numeric range queries in MetadataIndex — use numeric-aware comparison
instead of lexicographic string comparison for normalized values.
- Add data field to RelateParams and Relation types for relationship content.
- Add where.type → where.noun alias in metadata-only find() path.
- Rewrite README: focused ~350 lines from 791, quick start first, feature
showcase with mini-snippets, organized doc links, no version callouts.
- Add DATA_MODEL.md and QUERY_OPERATORS.md reference docs.
- Remove 10 outdated/redundant doc files consolidated into API reference.
- Improve JSDoc on Entity, Relation, AddParams, FindParams, and core methods.
- Fix tests asserting data properties appear in metadata (data model violation).
- Deprecate verb.source/target in favor of from/to (public) and sourceId/targetId (storage).
Plugins are no longer auto-detected. Updated README and PLUGINS.md
to show explicit plugins config, document all config values, and
fix manual registration example to use brain.use() API.
relate() was setting verb.source = fromEntity.type (a NounType like
"concept") instead of the entity UUID. Cortex's graph index indexes by
verb.source, so lookups by UUID found nothing — causing in-session
reads to return 0 results.
Also fixes:
- PathResolver calling private getIdsFromChunks() → public getIds()
- Plugin auto-detection removed; cortex loads only via explicit config
- GraphVerb types accept number timestamps and sourceId/targetId aliases
- Dead autoDetect() method removed from PluginRegistry
- In-session regression tests added for getRelations after relate()
GraphAdjacencyIndex.flush() was a no-op — LSM MemTables were never
written to SSTables for datasets under the 100K auto-flush threshold.
This caused readdir, getRelations, and getDescendants to return empty
results after close + reopen.
Three fixes:
- LSMTree.get(): merge MemTable + SSTable results (data spans both
after flush, old early-return missed SSTable data)
- GraphAdjacencyIndex.flush(): actually flush all 4 LSM-trees
- GraphAdjacencyIndex.close(): close all 4 trees (was only closing 2)
Also: brain.close() and shutdown hooks now call close() on graphIndex,
HNSW index, and metadataIndex to release timers and file handles.
Shutdown/close/flush now properly flushes all 4 components in parallel:
metadataIndex, graphIndex, HNSW dirty nodes, and storage counts. Previously
only counts were flushed, causing native provider data loss on restart.
Also:
- Wire roaring, msgpack, entityIdMapper provider consumption from plugins
- Fix allOf filter O(n²) intersection → O(n) Set-based
- Fix ne/exists negation filter to use Set-based exclusion
- Add setMsgpackImplementation() swap in SSTable for native msgpack
- Add setRoaringImplementation() swap for native CRoaring bitmaps
- Add getAllIntIds() to EntityIdMapper for bitmap operations
- Remove TypeAwareHNSWIndex from default index creation path
- Export memory detection utilities from internals
- Clean up 26 permanently-skipped dead tests
Fix critical wiring bugs that prevented plugin-provided implementations
from being used at runtime. All CRUD operations, fork/checkout/clear,
batch embedding, neural APIs, and VFS path resolution now properly
dispatch through the plugin registry.
Changes:
- Wire graphIndex to storage for getVerbsBySource() fast path
- Replace instanceof checks with duck-typing (indexIsTypeAware flag)
so plugin HNSW indexes work in add/update/delete/search
- Add createIndex() shared helper for plugin HNSW factory
- Fix fork/checkout/clear to use plugin factories for metadataIndex,
graphIndex, and HNSW instead of hardcoding JS constructors
- Add three-tier embedBatch priority: embedBatch > embeddings > WASM
- Skip WASM warmup/eagerEmbeddings when plugin provides embeddings
- Fix PathResolver metadataIndex access (was looking on storage)
- Use global UnifiedCache in SemanticPathResolver
- Wire plugin distance function through neural APIs
- Add diagnostics() method and CLI command for provider inspection
- Add requireProviders() for production fail-fast assertions
- Add init-time provider summary log
- Add plugin developer documentation (docs/PLUGINS.md)
- Export DiagnosticsResult type
- Wire PluginRegistry into Brainy init() with provider resolution for distance,
metadataIndex, graphIndex, embeddings, roaring, msgpack, and storage adapters
- Add setupStorage() factory that resolves storage:* providers from plugins before
falling back to built-in createStorage()
- Export internals API (setGlobalCache, UnifiedCache, EntityIdMapper, etc.) for
cortex plugin consumption
- Add plugin.test.ts verifying registration, activation, and provider resolution
- Deprecate browser support (OPFS, Web Workers, WASM embeddings) with warnings
in preparation for v8.0 server-only release
- FileSystemStorage: fix setupStorage resolution for mmap-filesystem provider
- Remove detectAndRepairCorruption from init() hot path — was loading all
metadata chunks sequentially on startup. Now available via checkHealth()
and repairIndex() methods.
- Short-circuit warmCache on empty workspace — skip 4-6 wasted storage reads.
- Parallelize MetadataIndex.init() and getGraphIndex() via Promise.all().
- Defer metadata writes during rebuild to batch boundaries (every 5000
entities) instead of flushing per-entity.
- Skip pre-reads for new entities in transactions — saves 2 storage
round-trips per add() on cloud storage.
- Switch vitest pool from threads to forks for process isolation
- Disable v8 coverage by default (causes vitest worker RPC timeout)
- Increase hookTimeout 30s to 60s for MetadataIndexManager init under load
- Reduce O(1) space test entity count, replace brittle timing assertions
- Add docs/guides/storage-adapters.md with verified batch config values
brain.add() was generating 26-40 immediate cloud writes per call, causing
HTTP 429 rate limit errors and high latency on GCS/S3/R2/Azure. Three-layer
fix: (1) deferred metadata writes with dirty-marking, (2) MetadataWriteBuffer
for write coalescing, (3) retry/backoff on all cloud storage adapters.
All metadata index files were stored under a single _system/ prefix, causing
per-prefix rate limiting on GCS/S3/R2/Azure during cold starts and bulk imports.
Distributes high-volume system keys across 256 sub-prefixes using FNV-1a hash.
Backward-compatible with legacy path fallback.
When rmdir({ recursive: true }) deleted a directory tree, child file paths
remained in contentCache and statCache, causing readFile() to return stale
data for deleted files. Adds recursive flag to invalidateCaches() that
evicts all descendant keys by prefix.
Uses embedBatch() to pre-compute all vectors in a single WASM forward
pass instead of N individual embed() calls. Items that already have
vectors are skipped.
Before: 100 entities = 100 separate WASM calls
After: 100 entities = 1 batched WASM call (micro-batched internally)
highlight() used Promise.race with a 10s timeout, but the losing
semantic phase promise continued running 25 WASM micro-batches,
saturating the event loop and degrading all subsequent operations
(find() going from ~200ms to ~10,000ms).
Add AbortController to highlight() so the semantic phase stops
immediately on timeout or error. Pass abort signal through
embedBatch() → EmbeddingManager → micro-batch loop.
Also add defensive hardening:
- CandleEmbeddingEngine: try/catch around WASM calls resets engine
state on failure so next call triggers re-initialization
- WASMEmbeddingEngine: initialize() now checks underlying Candle
engine state, not just its own flag, completing the recovery chain