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 +40 -0
- package/dist/stellavault.js +232 -167
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Stellavault
|
|
2
2
|
|
|
3
|
+
[](https://github.com/Evanciel/stellavault/actions/workflows/ci.yml) [](https://www.npmjs.com/package/stellavault) []()
|
|
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 |
|
package/dist/stellavault.js
CHANGED
|
@@ -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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
-
|
|
663
|
+
neighbors[i].push({ peer: j, sim });
|
|
664
|
+
neighbors[j].push({ peer: i, sim });
|
|
618
665
|
}
|
|
619
666
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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:
|
|
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
|
|
661
|
-
const vectors =
|
|
662
|
-
const k = Math.min(Math.max(5, Math.round(Math.sqrt(
|
|
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 <
|
|
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 ===
|
|
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 <
|
|
691
|
-
assignmentMap.set(
|
|
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
|
-
|
|
786
|
-
if (members.length === 0)
|
|
832
|
+
if (counts[c] === 0)
|
|
787
833
|
continue;
|
|
788
|
-
|
|
789
|
-
|
|
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
|
|
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
|
-
|
|
3090
|
-
|
|
3091
|
-
for (
|
|
3092
|
-
|
|
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/
|
|
3992
|
-
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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