stellavault 0.7.0 → 0.7.1

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
@@ -1,5 +1,7 @@
1
1
  # Stellavault
2
2
 
3
+ [![CI](https://github.com/Evanciel/stellavault/actions/workflows/ci.yml/badge.svg)](https://github.com/Evanciel/stellavault/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/stellavault)](https://www.npmjs.com/package/stellavault) [![tests](https://img.shields.io/badge/tests-177%20passing-brightgreen)]()
4
+
3
5
  > **Drop anything. It compiles itself into knowledge.** Claude remembers everything you know.
4
6
 
5
7
  Self-compiling knowledge base with a full-featured editor, 3D neural graph, AI-powered search, and spaced repetition — available as a **desktop app**, **CLI**, **Obsidian plugin**, and **MCP server**. Your vault files are never modified.
@@ -148,6 +150,18 @@ Claude can search, ask, draft, lint, and analyze your vault directly.
148
150
 
149
151
  ---
150
152
 
153
+ ## Try It Now (Demo Vault)
154
+
155
+ ```bash
156
+ npx stellavault index --vault ./examples/demo-vault # Index 10 sample notes
157
+ npx stellavault search "vector database" # Semantic search
158
+ npx stellavault graph # 3D graph visualization
159
+ ```
160
+
161
+ The demo vault includes interconnected notes about Vector Databases, Knowledge Graphs, Spaced Repetition, RAG, MCP, and more — perfect for exploring all features instantly.
162
+
163
+ ---
164
+
151
165
  ## Getting Started Guide
152
166
 
153
167
  ### Desktop App
@@ -199,6 +213,32 @@ stellavault decay # What are you forgetting?
199
213
 
200
214
  ---
201
215
 
216
+ ## Performance
217
+
218
+ Tested on synthetic vaults — all operations under 1 second for typical use cases:
219
+
220
+ | Operation | 100 docs | 500 docs | 1000 docs |
221
+ |-----------|----------|----------|-----------|
222
+ | Store init | 15ms | 15ms | 16ms |
223
+ | Bulk upsert | 12ms | 102ms | ~200ms |
224
+ | Search (BM25) | <1ms | <1ms | <1ms |
225
+ | Get all docs | <1ms | 2ms | ~4ms |
226
+ | 124K dot products | — | 36ms | — |
227
+
228
+ Run your own benchmarks:
229
+
230
+ ```bash
231
+ node tests/stress.mjs 500 # Test with 500 synthetic documents
232
+ ```
233
+
234
+ Key optimizations:
235
+ - Pre-normalized vectors: cosine similarity → single dot product
236
+ - Batched embedding loading (500/batch, prevents RAM overflow)
237
+ - Upper-triangle graph building (50% fewer comparisons)
238
+ - O(n) K-Means centroid updates with typed arrays
239
+
240
+ ---
241
+
202
242
  ## Tech Stack
203
243
 
204
244
  | Layer | Tech |
@@ -587,6 +587,50 @@ var init_indexer = __esm({
587
587
  }
588
588
  });
589
589
 
590
+ // packages/core/dist/utils/math.js
591
+ function cosineSimilarity(a, b) {
592
+ if (a.length !== b.length)
593
+ return 0;
594
+ let dot = 0, normA = 0, normB = 0;
595
+ for (let i = 0; i < a.length; i++) {
596
+ dot += a[i] * b[i];
597
+ normA += a[i] * a[i];
598
+ normB += b[i] * b[i];
599
+ }
600
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
601
+ return denom === 0 ? 0 : dot / denom;
602
+ }
603
+ function dotProduct(a, b) {
604
+ let sum = 0;
605
+ for (let i = 0; i < a.length; i++)
606
+ sum += a[i] * b[i];
607
+ return sum;
608
+ }
609
+ function normalizeVector(v) {
610
+ let norm = 0;
611
+ for (let i = 0; i < v.length; i++)
612
+ norm += v[i] * v[i];
613
+ norm = Math.sqrt(norm);
614
+ if (norm === 0)
615
+ return v;
616
+ for (let i = 0; i < v.length; i++)
617
+ v[i] /= norm;
618
+ return v;
619
+ }
620
+ function euclideanDist(a, b) {
621
+ let sum = 0;
622
+ for (let i = 0; i < a.length; i++) {
623
+ const diff = a[i] - b[i];
624
+ sum += diff * diff;
625
+ }
626
+ return Math.sqrt(sum);
627
+ }
628
+ var init_math = __esm({
629
+ "packages/core/dist/utils/math.js"() {
630
+ "use strict";
631
+ }
632
+ });
633
+
590
634
  // packages/core/dist/api/graph-data.js
591
635
  var graph_data_exports = {};
592
636
  __export(graph_data_exports, {
@@ -601,28 +645,32 @@ async function buildGraphData(store, options = {}) {
601
645
  ]);
602
646
  const edges = [];
603
647
  const edgeCounts = /* @__PURE__ */ new Map();
648
+ const normalizedVecs = /* @__PURE__ */ new Map();
604
649
  for (const doc of docs) {
605
650
  const vec = embeddings.get(doc.id);
606
651
  if (!vec)
607
652
  continue;
608
- const similarities = [];
609
- for (const other of docs) {
610
- if (other.id === doc.id)
611
- continue;
612
- const otherVec = embeddings.get(other.id);
613
- if (!otherVec)
614
- continue;
615
- const sim = cosineSimilarity(vec, otherVec);
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]);
616
662
  if (sim >= edgeThreshold) {
617
- similarities.push({ id: other.id, sim });
663
+ neighbors[i].push({ peer: j, sim });
664
+ neighbors[j].push({ peer: i, sim });
618
665
  }
619
666
  }
620
- similarities.sort((a, b) => b.sim - a.sim);
621
- const topK = similarities.slice(0, maxEdgesPerNode);
622
- for (const { id: targetId, sim } of topK) {
623
- const edgeKey = [doc.id, targetId].sort().join(":");
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}`;
624
672
  if (!edgeCounts.has(edgeKey)) {
625
- edges.push({ source: doc.id, target: targetId, weight: sim });
673
+ edges.push({ source: docIds[i], target: docIds[j], weight: sim });
626
674
  edgeCounts.set(edgeKey, 1);
627
675
  }
628
676
  }
@@ -657,16 +705,16 @@ async function buildGraphData(store, options = {}) {
657
705
  nodeCount: folderCounts.get(i) ?? 0
658
706
  }));
659
707
  } else {
660
- const docIds = docs.filter((d) => embeddings.has(d.id)).map((d) => d.id);
661
- const vectors = docIds.map((id) => embeddings.get(id));
662
- const k = Math.min(Math.max(5, Math.round(Math.sqrt(docIds.length / 5))), 10);
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);
663
711
  const assignments = kMeans(vectors, k);
664
712
  const clusterDocInfos = /* @__PURE__ */ new Map();
665
- for (let i = 0; i < docIds.length; i++) {
713
+ for (let i = 0; i < docIds2.length; i++) {
666
714
  const cId = assignments[i];
667
715
  if (!clusterDocInfos.has(cId))
668
716
  clusterDocInfos.set(cId, []);
669
- const doc = docs.find((d) => d.id === docIds[i]);
717
+ const doc = docs.find((d) => d.id === docIds2[i]);
670
718
  if (doc)
671
719
  clusterDocInfos.get(cId).push({ id: doc.id, title: doc.title });
672
720
  }
@@ -687,8 +735,8 @@ async function buildGraphData(store, options = {}) {
687
735
  });
688
736
  }
689
737
  assignmentMap = /* @__PURE__ */ new Map();
690
- for (let i = 0; i < docIds.length; i++) {
691
- assignmentMap.set(docIds[i], assignments[i]);
738
+ for (let i = 0; i < docIds2.length; i++) {
739
+ assignmentMap.set(docIds2[i], assignments[i]);
692
740
  }
693
741
  }
694
742
  const connectionCounts = /* @__PURE__ */ new Map();
@@ -725,16 +773,6 @@ async function buildGraphData(store, options = {}) {
725
773
  }
726
774
  };
727
775
  }
728
- function cosineSimilarity(a, b) {
729
- let dot = 0, normA = 0, normB = 0;
730
- for (let i = 0; i < a.length; i++) {
731
- dot += a[i] * b[i];
732
- normA += a[i] * a[i];
733
- normB += b[i] * b[i];
734
- }
735
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
736
- return denom === 0 ? 0 : dot / denom;
737
- }
738
776
  function kMeans(vectors, k, maxIter = 50) {
739
777
  if (vectors.length === 0)
740
778
  return [];
@@ -781,29 +819,30 @@ function kMeans(vectors, k, maxIter = 50) {
781
819
  }
782
820
  if (!changed)
783
821
  break;
822
+ const sums = Array.from({ length: k }, () => new Float64Array(dims));
823
+ const counts = new Uint32Array(k);
824
+ for (let i = 0; i < vectors.length; i++) {
825
+ const c = assignments[i];
826
+ counts[c]++;
827
+ const s = sums[c], v = vectors[i];
828
+ for (let d = 0; d < dims; d++)
829
+ s[d] += v[d];
830
+ }
784
831
  for (let c = 0; c < k; c++) {
785
- const members = vectors.filter((_, i) => assignments[i] === c);
786
- if (members.length === 0)
832
+ if (counts[c] === 0)
787
833
  continue;
788
- for (let d = 0; d < dims; d++) {
789
- centroids[c][d] = members.reduce((sum, v) => sum + v[d], 0) / members.length;
790
- }
834
+ const s = sums[c];
835
+ for (let d = 0; d < dims; d++)
836
+ centroids[c][d] = s[d] / counts[c];
791
837
  }
792
838
  }
793
839
  return assignments;
794
840
  }
795
- function euclideanDist(a, b) {
796
- let sum = 0;
797
- for (let i = 0; i < a.length; i++) {
798
- const diff = a[i] - b[i];
799
- sum += diff * diff;
800
- }
801
- return Math.sqrt(sum);
802
- }
803
841
  var CLUSTER_COLORS;
804
842
  var init_graph_data = __esm({
805
843
  "packages/core/dist/api/graph-data.js"() {
806
844
  "use strict";
845
+ init_math();
807
846
  CLUSTER_COLORS = [
808
847
  "#6366f1",
809
848
  "#ec4899",
@@ -824,6 +863,81 @@ var init_graph_data = __esm({
824
863
  }
825
864
  });
826
865
 
866
+ // packages/core/dist/intelligence/gap-detector.js
867
+ var gap_detector_exports = {};
868
+ __export(gap_detector_exports, {
869
+ detectKnowledgeGaps: () => detectKnowledgeGaps
870
+ });
871
+ async function detectKnowledgeGaps(store, graphData) {
872
+ const docs = await store.getAllDocuments();
873
+ const embeddings = await store.getDocumentEmbeddings();
874
+ let gd;
875
+ if (!graphData) {
876
+ const { buildGraphData: buildGraphData2 } = await Promise.resolve().then(() => (init_graph_data(), graph_data_exports));
877
+ gd = await buildGraphData2(store);
878
+ } else {
879
+ gd = graphData;
880
+ }
881
+ const { nodes, edges, clusters } = gd;
882
+ const clusterEdges = /* @__PURE__ */ new Map();
883
+ for (const edge of edges) {
884
+ const nodeA = nodes.find((n) => n.id === edge.source);
885
+ const nodeB = nodes.find((n) => n.id === edge.target);
886
+ if (!nodeA || !nodeB || nodeA.clusterId === nodeB.clusterId)
887
+ continue;
888
+ const key = [
889
+ Math.min(nodeA.clusterId, nodeB.clusterId),
890
+ Math.max(nodeA.clusterId, nodeB.clusterId)
891
+ ].join("-");
892
+ clusterEdges.set(key, (clusterEdges.get(key) ?? 0) + 1);
893
+ }
894
+ const gaps = [];
895
+ const clusterLabels = new Map(clusters.map((c) => [c.id, c.label]));
896
+ for (let i = 0; i < clusters.length; i++) {
897
+ for (let j = i + 1; j < clusters.length; j++) {
898
+ const key = `${Math.min(clusters[i].id, clusters[j].id)}-${Math.max(clusters[i].id, clusters[j].id)}`;
899
+ const bridgeCount = clusterEdges.get(key) ?? 0;
900
+ if (bridgeCount < 3) {
901
+ const labelA = clusterLabels.get(clusters[i].id) ?? `Cluster ${clusters[i].id}`;
902
+ const labelB = clusterLabels.get(clusters[j].id) ?? `Cluster ${clusters[j].id}`;
903
+ const nameA = labelA.replace(/\s*\(\d+\)$/, "");
904
+ const nameB = labelB.replace(/\s*\(\d+\)$/, "");
905
+ gaps.push({
906
+ clusterA: labelA,
907
+ clusterB: labelB,
908
+ bridgeCount,
909
+ suggestedTopic: `${nameA} + ${nameB} \uC5F0\uACB0 \uC9C0\uC2DD`,
910
+ severity: bridgeCount === 0 ? "high" : bridgeCount < 2 ? "medium" : "low"
911
+ });
912
+ }
913
+ }
914
+ }
915
+ const connectionCounts = /* @__PURE__ */ new Map();
916
+ for (const edge of edges) {
917
+ connectionCounts.set(edge.source, (connectionCounts.get(edge.source) ?? 0) + 1);
918
+ connectionCounts.set(edge.target, (connectionCounts.get(edge.target) ?? 0) + 1);
919
+ }
920
+ const isolatedNodes = nodes.filter((n) => (connectionCounts.get(n.id) ?? 0) <= 1).map((n) => ({
921
+ id: n.id,
922
+ title: n.label,
923
+ connections: connectionCounts.get(n.id) ?? 0
924
+ })).slice(0, 20);
925
+ return {
926
+ totalClusters: clusters.length,
927
+ totalGaps: gaps.filter((g) => g.severity !== "low").length,
928
+ gaps: gaps.sort((a, b) => {
929
+ const sev = { high: 0, medium: 1, low: 2 };
930
+ return sev[a.severity] - sev[b.severity];
931
+ }).slice(0, 15),
932
+ isolatedNodes
933
+ };
934
+ }
935
+ var init_gap_detector = __esm({
936
+ "packages/core/dist/intelligence/gap-detector.js"() {
937
+ "use strict";
938
+ }
939
+ });
940
+
827
941
  // packages/core/dist/intelligence/ask-engine.js
828
942
  var ask_engine_exports = {};
829
943
  __export(ask_engine_exports, {
@@ -1732,6 +1846,50 @@ var init_federation = __esm({
1732
1846
  }
1733
1847
  });
1734
1848
 
1849
+ // packages/core/dist/intelligence/duplicate-detector.js
1850
+ var duplicate_detector_exports = {};
1851
+ __export(duplicate_detector_exports, {
1852
+ detectDuplicates: () => detectDuplicates
1853
+ });
1854
+ async function detectDuplicates(store, threshold = 0.88, limit = 20) {
1855
+ const docs = await store.getAllDocuments();
1856
+ const embeddings = await store.getDocumentEmbeddings();
1857
+ if (docs.length < 2)
1858
+ return [];
1859
+ const docVecs = /* @__PURE__ */ new Map();
1860
+ for (const doc of docs) {
1861
+ const vec = embeddings.get(doc.id);
1862
+ if (!vec || vec.length === 0)
1863
+ continue;
1864
+ docVecs.set(doc.id, { vec: Array.from(vec), title: doc.title, filePath: doc.filePath });
1865
+ }
1866
+ const ids = [...docVecs.keys()];
1867
+ const pairs = [];
1868
+ for (let i = 0; i < ids.length; i++) {
1869
+ for (let j = i + 1; j < ids.length; j++) {
1870
+ const a = docVecs.get(ids[i]);
1871
+ const b = docVecs.get(ids[j]);
1872
+ const sim = cosineSimilarity(a.vec, b.vec);
1873
+ if (sim >= threshold) {
1874
+ pairs.push({
1875
+ docA: { id: ids[i], title: a.title, filePath: a.filePath },
1876
+ docB: { id: ids[j], title: b.title, filePath: b.filePath },
1877
+ similarity: Math.round(sim * 1e3) / 1e3
1878
+ });
1879
+ }
1880
+ }
1881
+ if (pairs.length >= limit * 2)
1882
+ break;
1883
+ }
1884
+ return pairs.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
1885
+ }
1886
+ var init_duplicate_detector = __esm({
1887
+ "packages/core/dist/intelligence/duplicate-detector.js"() {
1888
+ "use strict";
1889
+ init_math();
1890
+ }
1891
+ });
1892
+
1735
1893
  // packages/core/dist/i18n/note-strings.js
1736
1894
  var note_strings_exports = {};
1737
1895
  __export(note_strings_exports, {
@@ -3078,18 +3236,25 @@ function createSqliteVecStore(dbPath, dimensions = 384) {
3078
3236
  lastIndexed: lastRow?.indexed_at ?? null
3079
3237
  };
3080
3238
  },
3081
- async getDocumentEmbeddings() {
3082
- const rows = db.prepare(`
3239
+ async getDocumentEmbeddings(maxDocs = 1e4) {
3240
+ const BATCH_SIZE = 500;
3241
+ const result = /* @__PURE__ */ new Map();
3242
+ const stmt = db.prepare(`
3083
3243
  SELECT c.document_id, ce.embedding
3084
3244
  FROM chunks c
3085
3245
  JOIN chunk_embeddings ce ON ce.chunk_id = c.id
3086
3246
  WHERE c.id IN (
3087
3247
  SELECT MIN(id) FROM chunks GROUP BY document_id
3088
3248
  )
3089
- `).all();
3090
- const result = /* @__PURE__ */ new Map();
3091
- for (const row of rows) {
3092
- result.set(row.document_id, bufferToFloat32(row.embedding));
3249
+ LIMIT ? OFFSET ?
3250
+ `);
3251
+ for (let offset = 0; offset < maxDocs; offset += BATCH_SIZE) {
3252
+ const rows = stmt.all(Math.min(BATCH_SIZE, maxDocs - offset), offset);
3253
+ if (rows.length === 0)
3254
+ break;
3255
+ for (const row of rows) {
3256
+ result.set(row.document_id, bufferToFloat32(row.embedding));
3257
+ }
3093
3258
  }
3094
3259
  return result;
3095
3260
  },
@@ -3988,71 +4153,8 @@ var DecayEngine = class {
3988
4153
  }
3989
4154
  };
3990
4155
 
3991
- // packages/core/dist/intelligence/gap-detector.js
3992
- async function detectKnowledgeGaps(store, graphData) {
3993
- const docs = await store.getAllDocuments();
3994
- const embeddings = await store.getDocumentEmbeddings();
3995
- let gd;
3996
- if (!graphData) {
3997
- const { buildGraphData: buildGraphData2 } = await Promise.resolve().then(() => (init_graph_data(), graph_data_exports));
3998
- gd = await buildGraphData2(store);
3999
- } else {
4000
- gd = graphData;
4001
- }
4002
- const { nodes, edges, clusters } = gd;
4003
- const clusterEdges = /* @__PURE__ */ new Map();
4004
- for (const edge of edges) {
4005
- const nodeA = nodes.find((n) => n.id === edge.source);
4006
- const nodeB = nodes.find((n) => n.id === edge.target);
4007
- if (!nodeA || !nodeB || nodeA.clusterId === nodeB.clusterId)
4008
- continue;
4009
- const key = [
4010
- Math.min(nodeA.clusterId, nodeB.clusterId),
4011
- Math.max(nodeA.clusterId, nodeB.clusterId)
4012
- ].join("-");
4013
- clusterEdges.set(key, (clusterEdges.get(key) ?? 0) + 1);
4014
- }
4015
- const gaps = [];
4016
- const clusterLabels = new Map(clusters.map((c) => [c.id, c.label]));
4017
- for (let i = 0; i < clusters.length; i++) {
4018
- for (let j = i + 1; j < clusters.length; j++) {
4019
- const key = `${Math.min(clusters[i].id, clusters[j].id)}-${Math.max(clusters[i].id, clusters[j].id)}`;
4020
- const bridgeCount = clusterEdges.get(key) ?? 0;
4021
- if (bridgeCount < 3) {
4022
- const labelA = clusterLabels.get(clusters[i].id) ?? `Cluster ${clusters[i].id}`;
4023
- const labelB = clusterLabels.get(clusters[j].id) ?? `Cluster ${clusters[j].id}`;
4024
- const nameA = labelA.replace(/\s*\(\d+\)$/, "");
4025
- const nameB = labelB.replace(/\s*\(\d+\)$/, "");
4026
- gaps.push({
4027
- clusterA: labelA,
4028
- clusterB: labelB,
4029
- bridgeCount,
4030
- suggestedTopic: `${nameA} + ${nameB} \uC5F0\uACB0 \uC9C0\uC2DD`,
4031
- severity: bridgeCount === 0 ? "high" : bridgeCount < 2 ? "medium" : "low"
4032
- });
4033
- }
4034
- }
4035
- }
4036
- const connectionCounts = /* @__PURE__ */ new Map();
4037
- for (const edge of edges) {
4038
- connectionCounts.set(edge.source, (connectionCounts.get(edge.source) ?? 0) + 1);
4039
- connectionCounts.set(edge.target, (connectionCounts.get(edge.target) ?? 0) + 1);
4040
- }
4041
- const isolatedNodes = nodes.filter((n) => (connectionCounts.get(n.id) ?? 0) <= 1).map((n) => ({
4042
- id: n.id,
4043
- title: n.label,
4044
- connections: connectionCounts.get(n.id) ?? 0
4045
- })).slice(0, 20);
4046
- return {
4047
- totalClusters: clusters.length,
4048
- totalGaps: gaps.filter((g) => g.severity !== "low").length,
4049
- gaps: gaps.sort((a, b) => {
4050
- const sev = { high: 0, medium: 1, low: 2 };
4051
- return sev[a.severity] - sev[b.severity];
4052
- }).slice(0, 15),
4053
- isolatedNodes
4054
- };
4055
- }
4156
+ // packages/core/dist/mcp/tools/learning-path.js
4157
+ init_gap_detector();
4056
4158
 
4057
4159
  // packages/core/dist/intelligence/learning-path.js
4058
4160
  function generateLearningPath(input, limit = 15) {
@@ -4144,6 +4246,7 @@ function createLearningPathTool(store) {
4144
4246
  }
4145
4247
 
4146
4248
  // packages/core/dist/mcp/tools/detect-gaps.js
4249
+ init_gap_detector();
4147
4250
  function createDetectGapsTool(store) {
4148
4251
  return {
4149
4252
  name: "detect-gaps",
@@ -4643,7 +4746,7 @@ function createMcpServer(options) {
4643
4746
  const askTool = createAskTool(searchEngine, vaultPath);
4644
4747
  const generateDraftTool = createGenerateDraftTool(searchEngine, vaultPath);
4645
4748
  const agenticTools = embedder ? createAgenticGraphTools(store, embedder, vaultPath) : [];
4646
- const server = new Server({ name: "stellavault", version: "0.7.0" }, { capabilities: { tools: {} } });
4749
+ const server = new Server({ name: "stellavault", version: "0.7.1" }, { capabilities: { tools: {} } });
4647
4750
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
4648
4751
  tools: [
4649
4752
  searchToolDef,
@@ -5090,55 +5193,9 @@ function createFederationRouter(store) {
5090
5193
  }
5091
5194
 
5092
5195
  // packages/core/dist/api/routes/knowledge.js
5196
+ init_duplicate_detector();
5197
+ init_gap_detector();
5093
5198
  import { Router as Router2 } from "express";
5094
-
5095
- // packages/core/dist/intelligence/duplicate-detector.js
5096
- async function detectDuplicates(store, threshold = 0.88, limit = 20) {
5097
- const docs = await store.getAllDocuments();
5098
- const embeddings = await store.getDocumentEmbeddings();
5099
- if (docs.length < 2)
5100
- return [];
5101
- const docVecs = /* @__PURE__ */ new Map();
5102
- for (const doc of docs) {
5103
- const vec = embeddings.get(doc.id);
5104
- if (!vec || vec.length === 0)
5105
- continue;
5106
- docVecs.set(doc.id, { vec: Array.from(vec), title: doc.title, filePath: doc.filePath });
5107
- }
5108
- const ids = [...docVecs.keys()];
5109
- const pairs = [];
5110
- for (let i = 0; i < ids.length; i++) {
5111
- for (let j = i + 1; j < ids.length; j++) {
5112
- const a = docVecs.get(ids[i]);
5113
- const b = docVecs.get(ids[j]);
5114
- const sim = cosineSimilarity2(a.vec, b.vec);
5115
- if (sim >= threshold) {
5116
- pairs.push({
5117
- docA: { id: ids[i], title: a.title, filePath: a.filePath },
5118
- docB: { id: ids[j], title: b.title, filePath: b.filePath },
5119
- similarity: Math.round(sim * 1e3) / 1e3
5120
- });
5121
- }
5122
- }
5123
- if (pairs.length >= limit * 2)
5124
- break;
5125
- }
5126
- return pairs.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
5127
- }
5128
- function cosineSimilarity2(a, b) {
5129
- if (a.length !== b.length)
5130
- return 0;
5131
- let dot = 0, normA = 0, normB = 0;
5132
- for (let i = 0; i < a.length; i++) {
5133
- dot += a[i] * b[i];
5134
- normA += a[i] * a[i];
5135
- normB += b[i] * b[i];
5136
- }
5137
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
5138
- return denom === 0 ? 0 : dot / denom;
5139
- }
5140
-
5141
- // packages/core/dist/api/routes/knowledge.js
5142
5199
  function createKnowledgeRouter(opts) {
5143
5200
  const { store, searchEngine, vaultPath, requireAuth } = opts;
5144
5201
  const router = Router2();
@@ -6036,7 +6093,8 @@ function createApiServer(options) {
6036
6093
  }
6037
6094
  let gapSummary = { gapCount: 0, isolatedCount: 0 };
6038
6095
  try {
6039
- const gapReport = await detectKnowledgeGaps(store);
6096
+ const { detectKnowledgeGaps: detectKnowledgeGaps2 } = await Promise.resolve().then(() => (init_gap_detector(), gap_detector_exports));
6097
+ const gapReport = await detectKnowledgeGaps2(store);
6040
6098
  gapSummary = {
6041
6099
  gapCount: gapReport.gaps?.length ?? 0,
6042
6100
  isolatedCount: gapReport.isolatedNodes?.length ?? 0
@@ -6046,7 +6104,8 @@ function createApiServer(options) {
6046
6104
  }
6047
6105
  let dupCount = 0;
6048
6106
  try {
6049
- const pairs = await detectDuplicates(store, 0.88, 50);
6107
+ const { detectDuplicates: detectDuplicates2 } = await Promise.resolve().then(() => (init_duplicate_detector(), duplicate_detector_exports));
6108
+ const pairs = await detectDuplicates2(store, 0.88, 50);
6050
6109
  dupCount = pairs.length;
6051
6110
  } catch (e) {
6052
6111
  console.error("[health] Duplicate detection failed:", e instanceof Error ? e.message : e);
@@ -6243,6 +6302,10 @@ function createApiServer(options) {
6243
6302
  };
6244
6303
  }
6245
6304
 
6305
+ // packages/core/dist/index.js
6306
+ init_duplicate_detector();
6307
+ init_gap_detector();
6308
+
6246
6309
  // packages/core/dist/intelligence/contradiction-detector.js
6247
6310
  var NEGATION_PAIRS = [
6248
6311
  ["should", "should not"],
@@ -6360,6 +6423,8 @@ init_ask_engine();
6360
6423
  init_wiki_compiler();
6361
6424
 
6362
6425
  // packages/core/dist/intelligence/knowledge-lint.js
6426
+ init_gap_detector();
6427
+ init_duplicate_detector();
6363
6428
  async function lintKnowledge(store) {
6364
6429
  const issues = [];
6365
6430
  const suggestions = [];
@@ -9564,7 +9629,7 @@ if (nodeVersion < 20) {
9564
9629
  process.exit(1);
9565
9630
  }
9566
9631
  var program = new Command();
9567
- var SV_VERSION = true ? "0.7.0" : "0.0.0-dev";
9632
+ var SV_VERSION = true ? "0.7.1" : "0.0.0-dev";
9568
9633
  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");
9569
9634
  program.command("init").description("Interactive setup wizard \u2014 get started in 3 minutes").action(initCommand);
9570
9635
  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.0",
3
+ "version": "0.7.1",
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",