stellavault 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -232,9 +232,10 @@ node tests/stress.mjs 500 # Test with 500 synthetic documents
232
232
  ```
233
233
 
234
234
  Key optimizations:
235
+ - **HNSW graph building** — sqlite-vec KNN for 200+ docs (O(n·K·log n) vs O(n²))
235
236
  - Pre-normalized vectors: cosine similarity → single dot product
236
237
  - Batched embedding loading (500/batch, prevents RAM overflow)
237
- - Upper-triangle graph building (50% fewer comparisons)
238
+ - Upper-triangle brute-force for small vaults (< 200 docs)
238
239
  - O(n) K-Means centroid updates with typed arrays
239
240
 
240
241
  ---
@@ -645,33 +645,50 @@ async function buildGraphData(store, options = {}) {
645
645
  ]);
646
646
  const edges = [];
647
647
  const edgeCounts = /* @__PURE__ */ new Map();
648
- const normalizedVecs = /* @__PURE__ */ new Map();
649
- for (const doc of docs) {
650
- const vec = embeddings.get(doc.id);
651
- if (!vec)
652
- continue;
653
- normalizedVecs.set(doc.id, normalizeVector([...vec]));
654
- }
655
- const docIds = [...normalizedVecs.keys()];
656
- const vecArray = docIds.map((id) => normalizedVecs.get(id));
657
- const n = docIds.length;
658
- const neighbors = Array.from({ length: n }, () => []);
659
- for (let i = 0; i < n; i++) {
660
- for (let j = i + 1; j < n; j++) {
661
- const sim = dotProduct(vecArray[i], vecArray[j]);
662
- if (sim >= edgeThreshold) {
663
- neighbors[i].push({ peer: j, sim });
664
- neighbors[j].push({ peer: i, sim });
648
+ const USE_HNSW_THRESHOLD = 200;
649
+ const docsWithVecs = docs.filter((d) => embeddings.has(d.id));
650
+ if (docsWithVecs.length > USE_HNSW_THRESHOLD) {
651
+ for (const doc of docsWithVecs) {
652
+ const vec = embeddings.get(doc.id);
653
+ const neighbors = await store.findDocumentNeighbors(vec, maxEdgesPerNode + 1);
654
+ for (const { documentId: targetId, similarity } of neighbors) {
655
+ if (targetId === doc.id)
656
+ continue;
657
+ if (similarity < edgeThreshold)
658
+ continue;
659
+ const edgeKey = [doc.id, targetId].sort().join(":");
660
+ if (!edgeCounts.has(edgeKey)) {
661
+ edges.push({ source: doc.id, target: targetId, weight: similarity });
662
+ edgeCounts.set(edgeKey, 1);
663
+ }
665
664
  }
666
665
  }
667
- }
668
- for (let i = 0; i < n; i++) {
669
- neighbors[i].sort((a, b) => b.sim - a.sim);
670
- for (const { peer: j, sim } of neighbors[i].slice(0, maxEdgesPerNode)) {
671
- const edgeKey = i < j ? `${i}:${j}` : `${j}:${i}`;
672
- if (!edgeCounts.has(edgeKey)) {
673
- edges.push({ source: docIds[i], target: docIds[j], weight: sim });
674
- edgeCounts.set(edgeKey, 1);
666
+ } else {
667
+ const normalizedVecs = /* @__PURE__ */ new Map();
668
+ for (const doc of docsWithVecs) {
669
+ normalizedVecs.set(doc.id, normalizeVector([...embeddings.get(doc.id)]));
670
+ }
671
+ const docIds = [...normalizedVecs.keys()];
672
+ const vecArray = docIds.map((id) => normalizedVecs.get(id));
673
+ const n = docIds.length;
674
+ const neighbors = Array.from({ length: n }, () => []);
675
+ for (let i = 0; i < n; i++) {
676
+ for (let j = i + 1; j < n; j++) {
677
+ const sim = dotProduct(vecArray[i], vecArray[j]);
678
+ if (sim >= edgeThreshold) {
679
+ neighbors[i].push({ peer: j, sim });
680
+ neighbors[j].push({ peer: i, sim });
681
+ }
682
+ }
683
+ }
684
+ for (let i = 0; i < n; i++) {
685
+ neighbors[i].sort((a, b) => b.sim - a.sim);
686
+ for (const { peer: j, sim } of neighbors[i].slice(0, maxEdgesPerNode)) {
687
+ const edgeKey = i < j ? `${i}:${j}` : `${j}:${i}`;
688
+ if (!edgeCounts.has(edgeKey)) {
689
+ edges.push({ source: docIds[i], target: docIds[j], weight: sim });
690
+ edgeCounts.set(edgeKey, 1);
691
+ }
675
692
  }
676
693
  }
677
694
  }
@@ -705,16 +722,16 @@ async function buildGraphData(store, options = {}) {
705
722
  nodeCount: folderCounts.get(i) ?? 0
706
723
  }));
707
724
  } else {
708
- const docIds2 = docs.filter((d) => embeddings.has(d.id)).map((d) => d.id);
709
- const vectors = docIds2.map((id) => embeddings.get(id));
710
- const k = Math.min(Math.max(5, Math.round(Math.sqrt(docIds2.length / 5))), 10);
725
+ const docIds = docs.filter((d) => embeddings.has(d.id)).map((d) => d.id);
726
+ const vectors = docIds.map((id) => embeddings.get(id));
727
+ const k = Math.min(Math.max(5, Math.round(Math.sqrt(docIds.length / 5))), 10);
711
728
  const assignments = kMeans(vectors, k);
712
729
  const clusterDocInfos = /* @__PURE__ */ new Map();
713
- for (let i = 0; i < docIds2.length; i++) {
730
+ for (let i = 0; i < docIds.length; i++) {
714
731
  const cId = assignments[i];
715
732
  if (!clusterDocInfos.has(cId))
716
733
  clusterDocInfos.set(cId, []);
717
- const doc = docs.find((d) => d.id === docIds2[i]);
734
+ const doc = docs.find((d) => d.id === docIds[i]);
718
735
  if (doc)
719
736
  clusterDocInfos.get(cId).push({ id: doc.id, title: doc.title });
720
737
  }
@@ -735,8 +752,8 @@ async function buildGraphData(store, options = {}) {
735
752
  });
736
753
  }
737
754
  assignmentMap = /* @__PURE__ */ new Map();
738
- for (let i = 0; i < docIds2.length; i++) {
739
- assignmentMap.set(docIds2[i], assignments[i]);
755
+ for (let i = 0; i < docIds.length; i++) {
756
+ assignmentMap.set(docIds[i], assignments[i]);
740
757
  }
741
758
  }
742
759
  const connectionCounts = /* @__PURE__ */ new Map();
@@ -3258,6 +3275,21 @@ function createSqliteVecStore(dbPath, dimensions = 384) {
3258
3275
  }
3259
3276
  return result;
3260
3277
  },
3278
+ async findDocumentNeighbors(embedding, limit) {
3279
+ const rows = db.prepare(`
3280
+ SELECT c.document_id, MIN(ce.distance) as distance
3281
+ FROM chunk_embeddings ce
3282
+ JOIN chunks c ON c.id = ce.chunk_id
3283
+ WHERE ce.embedding MATCH ?
3284
+ GROUP BY c.document_id
3285
+ ORDER BY distance
3286
+ LIMIT ?
3287
+ `).all(float32Buffer(embedding), limit * 2);
3288
+ return rows.slice(0, limit).map((r) => ({
3289
+ documentId: r.document_id,
3290
+ similarity: 1 / (1 + r.distance)
3291
+ }));
3292
+ },
3261
3293
  async close() {
3262
3294
  db.close();
3263
3295
  },
@@ -4746,7 +4778,7 @@ function createMcpServer(options) {
4746
4778
  const askTool = createAskTool(searchEngine, vaultPath);
4747
4779
  const generateDraftTool = createGenerateDraftTool(searchEngine, vaultPath);
4748
4780
  const agenticTools = embedder ? createAgenticGraphTools(store, embedder, vaultPath) : [];
4749
- const server = new Server({ name: "stellavault", version: "0.7.2" }, { capabilities: { tools: {} } });
4781
+ const server = new Server({ name: "stellavault", version: "0.7.3" }, { capabilities: { tools: {} } });
4750
4782
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
4751
4783
  tools: [
4752
4784
  searchToolDef,
@@ -6902,6 +6934,7 @@ var CREDITS_FILE = join19(homedir11(), ".stellavault", "federation", "credits.js
6902
6934
 
6903
6935
  // packages/core/dist/index.js
6904
6936
  init_retry();
6937
+ init_math();
6905
6938
  init_indexer();
6906
6939
  function createKnowledgeHub(config) {
6907
6940
  const embedder = createLocalEmbedder(config.embedding.localModel);
@@ -9647,7 +9680,7 @@ if (nodeVersion < 20) {
9647
9680
  process.exit(1);
9648
9681
  }
9649
9682
  var program = new Command();
9650
- var SV_VERSION = true ? "0.7.2" : "0.0.0-dev";
9683
+ var SV_VERSION = true ? "0.7.3" : "0.0.0-dev";
9651
9684
  program.name("stellavault").description("Stellavault \u2014 Self-compiling knowledge base for your Obsidian vault").version(SV_VERSION).option("--json", "Output in JSON format (for scripting)").option("--quiet", "Suppress non-essential output");
9652
9685
  program.command("init").description("Interactive setup wizard \u2014 get started in 3 minutes").action(initCommand);
9653
9686
  program.command("doctor").description("Diagnose setup issues (config, vault, DB, model, Node version)").action(doctorCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stellavault",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Drop anything. It compiles itself into knowledge. Claude remembers everything you know. Local-first MCP server, vault files never modified.",
5
5
  "repository": {
6
6
  "type": "git",