@monoes/monograph 1.2.8 → 1.2.9

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.
Files changed (72) hide show
  1. package/dist/src/index.d.ts +0 -7
  2. package/dist/src/index.d.ts.map +1 -1
  3. package/dist/src/index.js +0 -7
  4. package/dist/src/index.js.map +1 -1
  5. package/dist/src/mcp-tools/index.d.ts +0 -1
  6. package/dist/src/mcp-tools/index.d.ts.map +1 -1
  7. package/dist/src/mcp-tools/index.js +0 -1
  8. package/dist/src/mcp-tools/index.js.map +1 -1
  9. package/dist/src/search/hybrid-query.d.ts +11 -17
  10. package/dist/src/search/hybrid-query.d.ts.map +1 -1
  11. package/dist/src/search/hybrid-query.js +10 -95
  12. package/dist/src/search/hybrid-query.js.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +1 -1
  15. package/src/index.ts +0 -7
  16. package/src/mcp-tools/index.ts +0 -1
  17. package/src/search/hybrid-query.ts +13 -120
  18. package/dist/src/__tests__/mcp-tools/embed.test.d.ts +0 -2
  19. package/dist/src/__tests__/mcp-tools/embed.test.d.ts.map +0 -1
  20. package/dist/src/__tests__/mcp-tools/embed.test.js +0 -12
  21. package/dist/src/__tests__/mcp-tools/embed.test.js.map +0 -1
  22. package/dist/src/__tests__/search/embed-stale.test.d.ts +0 -2
  23. package/dist/src/__tests__/search/embed-stale.test.d.ts.map +0 -1
  24. package/dist/src/__tests__/search/embed-stale.test.js +0 -38
  25. package/dist/src/__tests__/search/embed-stale.test.js.map +0 -1
  26. package/dist/src/__tests__/search/exact-search.test.d.ts +0 -2
  27. package/dist/src/__tests__/search/exact-search.test.d.ts.map +0 -1
  28. package/dist/src/__tests__/search/exact-search.test.js +0 -49
  29. package/dist/src/__tests__/search/exact-search.test.js.map +0 -1
  30. package/dist/src/mcp-tools/embed.d.ts +0 -25
  31. package/dist/src/mcp-tools/embed.d.ts.map +0 -1
  32. package/dist/src/mcp-tools/embed.js +0 -33
  33. package/dist/src/mcp-tools/embed.js.map +0 -1
  34. package/dist/src/pipeline/phases/embeddings.d.ts +0 -6
  35. package/dist/src/pipeline/phases/embeddings.d.ts.map +0 -1
  36. package/dist/src/pipeline/phases/embeddings.js +0 -12
  37. package/dist/src/pipeline/phases/embeddings.js.map +0 -1
  38. package/dist/src/search/device-config.d.ts +0 -16
  39. package/dist/src/search/device-config.d.ts.map +0 -1
  40. package/dist/src/search/device-config.js +0 -22
  41. package/dist/src/search/device-config.js.map +0 -1
  42. package/dist/src/search/embed-batch.d.ts +0 -30
  43. package/dist/src/search/embed-batch.d.ts.map +0 -1
  44. package/dist/src/search/embed-batch.js +0 -99
  45. package/dist/src/search/embed-batch.js.map +0 -1
  46. package/dist/src/search/embedder.d.ts +0 -26
  47. package/dist/src/search/embedder.d.ts.map +0 -1
  48. package/dist/src/search/embedder.js +0 -49
  49. package/dist/src/search/embedder.js.map +0 -1
  50. package/dist/src/search/exact-search.d.ts +0 -10
  51. package/dist/src/search/exact-search.d.ts.map +0 -1
  52. package/dist/src/search/exact-search.js +0 -28
  53. package/dist/src/search/exact-search.js.map +0 -1
  54. package/dist/src/search/http-embedder.d.ts +0 -17
  55. package/dist/src/search/http-embedder.d.ts.map +0 -1
  56. package/dist/src/search/http-embedder.js +0 -57
  57. package/dist/src/search/http-embedder.js.map +0 -1
  58. package/dist/src/storage/embedding-store.d.ts +0 -29
  59. package/dist/src/storage/embedding-store.d.ts.map +0 -1
  60. package/dist/src/storage/embedding-store.js +0 -77
  61. package/dist/src/storage/embedding-store.js.map +0 -1
  62. package/src/__tests__/mcp-tools/embed.test.ts +0 -13
  63. package/src/__tests__/search/embed-stale.test.ts +0 -42
  64. package/src/__tests__/search/exact-search.test.ts +0 -54
  65. package/src/mcp-tools/embed.ts +0 -56
  66. package/src/pipeline/phases/embeddings.ts +0 -17
  67. package/src/search/device-config.ts +0 -31
  68. package/src/search/embed-batch.ts +0 -151
  69. package/src/search/embedder.ts +0 -60
  70. package/src/search/exact-search.ts +0 -45
  71. package/src/search/http-embedder.ts +0 -78
  72. package/src/storage/embedding-store.ts +0 -108
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monograph",
3
- "version": "1.2.8",
3
+ "version": "1.2.9",
4
4
  "type": "module",
5
5
  "description": "Native TypeScript code intelligence engine for monomind",
6
6
  "main": "dist/src/index.js",
package/src/index.ts CHANGED
@@ -18,12 +18,8 @@ export * from './export/graphml.js';
18
18
  export * from './export/svg.js';
19
19
  export * from './mcp-tools/index.js';
20
20
  export * from './staleness/git-staleness.js';
21
- export * from './storage/embedding-store.js';
22
21
  export * from './search/rrf.js';
23
- export * from './search/embedder.js';
24
- export * from './search/embed-batch.js';
25
22
  export * from './search/hybrid-query.js';
26
- export * from './mcp-tools/embed.js';
27
23
  export * from './groups/group-config.js';
28
24
  export * from './groups/group-search.js';
29
25
  export * from './groups/contract-bridge.js';
@@ -72,8 +68,6 @@ export * from './analysis/file-classifier.js';
72
68
  export * from './pipeline/phases/evidence.js';
73
69
  export * from './registry/repo-registry.js';
74
70
  export * from './search/ast-chunker.js';
75
- export * from './search/http-embedder.js';
76
- export * from './search/device-config.js';
77
71
  export * from './wiki/providers.js';
78
72
  export * from './pipeline/phases/variables-phase.js';
79
73
  export * from './pipeline/phases/wildcard-phase.js';
@@ -94,7 +88,6 @@ export * from './mcp-tools/query.js';
94
88
  export * from './search/diacritic.js';
95
89
  export * from './ingest/url-ingest.js';
96
90
  export * from './analysis/corpus-health.js';
97
- export * from './search/exact-search.js';
98
91
  export * from './groups/grpc-extractor.js';
99
92
  export * from './groups/topic-extractor.js';
100
93
  export * from './groups/service-boundary.js';
@@ -16,7 +16,6 @@ export * from './shape-check.js';
16
16
  export * from './neighbors.js';
17
17
  export * from './graph-query.js';
18
18
  export * from './explain.js';
19
- export * from './embed.js';
20
19
  export * from './group-sync.js';
21
20
  export * from './list-repos.js';
22
21
  export * from './query.js';
@@ -1,27 +1,23 @@
1
1
  /**
2
- * Hybrid BM25 + vector search with Reciprocal Rank Fusion.
2
+ * BM25 search (FTS5) with the historical "hybrid" entry-point signature.
3
3
  *
4
- * When MONOGRAPH_EMBEDDINGS=true (or an embedder is provided), the query is
5
- * embedded and cosine similarity is computed in JavaScript against all stored
6
- * vectors. Both result lists are merged via RRF(K=60).
7
- *
8
- * If embeddings are unavailable (table empty, env not set, no embedder),
9
- * the function falls back to BM25 only — same behaviour as before.
4
+ * Monograph search is BM25-only. The former embedding/vector branch
5
+ * (gated on MONOGRAPH_EMBEDDINGS=true) was removed it was never run in
6
+ * practice (the embeddings table stayed empty) and did a JS cosine full
7
+ * scan. The exported function signatures are unchanged so callers do not
8
+ * need to change.
10
9
  */
11
10
 
12
11
  import type Database from 'better-sqlite3';
13
- import type { EmbedderFn } from './embedder.js';
14
- import { mergeRanks, type RankedResult } from './rrf.js';
15
- import { getAllEmbeddings, countEmbeddings } from '../storage/embedding-store.js';
16
- import { embedText } from './embedder.js';
12
+ import type { RankedResult } from './rrf.js';
17
13
  import { ftsSearch } from '../storage/fts-store.js';
18
14
  import { normalizeSearchTerm } from './diacritic.js';
19
15
 
20
16
  export interface HybridQueryOptions {
21
17
  limit?: number;
22
18
  label?: string;
23
- /** Explicit embedderoverrides MONOGRAPH_EMBEDDINGS env check */
24
- embedder?: EmbedderFn;
19
+ /** @deprecated Embeddings are disabled this option is ignored; search is BM25-only. */
20
+ embedder?: unknown;
25
21
  }
26
22
 
27
23
  export interface HybridResult extends RankedResult {
@@ -38,19 +34,15 @@ export interface HybridResult extends RankedResult {
38
34
  }
39
35
 
40
36
  /**
41
- * Run a hybrid BM25 + cosine search.
42
- *
43
- * Falls back to BM25-only when:
44
- * - MONOGRAPH_EMBEDDINGS env var is not 'true' AND no explicit embedder is given
45
- * - The embeddings table is empty
46
- * - Embedding the query string fails
37
+ * Run a BM25 (FTS5) search. Despite the name, this is BM25-only — the
38
+ * vector branch was removed; the `embedder` option is ignored.
47
39
  */
48
40
  export async function hybridQuery(
49
41
  db: Database.Database,
50
42
  query: string,
51
43
  options: HybridQueryOptions = {},
52
44
  ): Promise<HybridResult[]> {
53
- const { limit = 20, label, embedder } = options;
45
+ const { limit = 20, label } = options;
54
46
 
55
47
  // Normalize the query for text-based lookups (strip diacritics, lowercase, trim)
56
48
  const normalizedQuery = normalizeSearchTerm(query);
@@ -69,104 +61,5 @@ export async function hybridQuery(
69
61
  endLine: r.endLine,
70
62
  }));
71
63
 
72
- // ── Decide whether to add vector results ──────────────────────────────────
73
- const embeddingsEnabled =
74
- embedder !== undefined || process.env['MONOGRAPH_EMBEDDINGS'] === 'true';
75
-
76
- if (!embeddingsEnabled) return sliceHybrid(bm25Results, limit);
77
-
78
- const embeddingCount = countEmbeddings(db);
79
- if (embeddingCount === 0) return sliceHybrid(bm25Results, limit);
80
-
81
- // ── Embed the query ────────────────────────────────────────────────────────
82
- let queryVec: Float32Array;
83
- try {
84
- const fn: import('./embedder.js').EmbedderFn = embedder ?? (await import('./embedder.js').then((m) => m.getEmbedder()));
85
- queryVec = await embedText(normalizedQuery, fn);
86
- } catch {
87
- // Embedding failed — degrade gracefully to BM25
88
- return sliceHybrid(bm25Results, limit);
89
- }
90
-
91
- // ── Cosine similarity against stored vectors ──────────────────────────────
92
- const allVecs = getAllEmbeddings(db);
93
- const vectorScored: { id: string; cosine: number }[] = [];
94
-
95
- for (const [nodeId, vec] of allVecs) {
96
- const sim = dotProduct(queryVec, vec); // valid because vectors are L2-normalised
97
- vectorScored.push({ id: nodeId, cosine: sim });
98
- }
99
-
100
- // Sort descending by cosine similarity
101
- vectorScored.sort((a, b) => b.cosine - a.cosine);
102
-
103
- // Build vector RankedResult list with node metadata joined from BM25 cache
104
- const bm25ById = new Map(bm25Results.map((r) => [r.id, r]));
105
- const vectorResults: RankedResult[] = vectorScored.map((vs) => {
106
- const meta = bm25ById.get(vs.id);
107
- return {
108
- id: vs.id,
109
- name: (meta?.name as string | undefined) ?? '',
110
- normLabel: (meta?.normLabel as string | undefined) ?? '',
111
- filePath: (meta?.filePath as string | null | undefined) ?? null,
112
- label: (meta?.label as string | undefined) ?? '',
113
- score: vs.cosine,
114
- };
115
- });
116
-
117
- // ── Merge via RRF ──────────────────────────────────────────────────────────
118
- const merged = mergeRanks(bm25Results, vectorResults);
119
-
120
- // Enrich nodes not in bm25 cache with a DB lookup
121
- const enrichable = merged as Partial<HybridResult>[];
122
- const unknownIds = enrichable
123
- .filter((r) => !r.name)
124
- .map((r) => r.id as string);
125
-
126
- if (unknownIds.length > 0) {
127
- const placeholders = unknownIds.map(() => '?').join(',');
128
- const rows = db
129
- .prepare(
130
- `SELECT id, name, norm_label, file_path, label, start_line, end_line FROM nodes WHERE id IN (${placeholders})`,
131
- )
132
- .all(...unknownIds) as {
133
- id: string;
134
- name: string;
135
- norm_label: string;
136
- file_path: string | null;
137
- label: string;
138
- start_line: number | null;
139
- end_line: number | null;
140
- }[];
141
-
142
- const rowMap = new Map(rows.map((r) => [r.id, r]));
143
- for (const item of enrichable) {
144
- if (!item.name) {
145
- const row = rowMap.get(item.id as string);
146
- if (row) {
147
- item.name = row.name;
148
- item.normLabel = row.norm_label;
149
- item.filePath = row.file_path;
150
- item.label = row.label;
151
- (item as HybridResult).startLine = row.start_line;
152
- (item as HybridResult).endLine = row.end_line;
153
- }
154
- }
155
- }
156
- }
157
-
158
- return sliceHybrid(merged, limit);
159
- }
160
-
161
- // ── Helpers ───────────────────────────────────────────────────────────────────
162
-
163
- function sliceHybrid(results: RankedResult[], limit: number): HybridResult[] {
164
- return results.slice(0, limit) as HybridResult[];
165
- }
166
-
167
- function dotProduct(a: Float32Array, b: Float32Array): number {
168
- let sum = 0;
169
- const len = Math.min(a.length, b.length);
170
- for (let i = 0; i < len; i++) sum += a[i] * b[i];
171
- return sum;
64
+ return bm25Results.slice(0, limit) as HybridResult[];
172
65
  }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=embed.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embed.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/mcp-tools/embed.test.ts"],"names":[],"mappings":""}
@@ -1,12 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- describe('embed tool exports', () => {
3
- it('runEmbed is exported from mcp-tools index', async () => {
4
- const mod = await import('../../mcp-tools/index.js');
5
- expect(typeof mod.runEmbed).toBe('function');
6
- });
7
- it('runEmbed is directly importable from embed.js', async () => {
8
- const { runEmbed } = await import('../../mcp-tools/embed.js');
9
- expect(typeof runEmbed).toBe('function');
10
- });
11
- });
12
- //# sourceMappingURL=embed.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embed.test.js","sourceRoot":"","sources":["../../../../src/__tests__/mcp-tools/embed.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;QACrD,MAAM,CAAC,OAAQ,GAAW,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;QAC9D,MAAM,CAAC,OAAO,QAAQ,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=embed-stale.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embed-stale.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/search/embed-stale.test.ts"],"names":[],"mappings":""}
@@ -1,38 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import Database from 'better-sqlite3';
3
- import { upsertEmbedding, getEmbeddingContentHash, isEmbeddingStale } from '../../storage/embedding-store.js';
4
- function makeDb() {
5
- const db = new Database(':memory:');
6
- db.exec(`
7
- CREATE TABLE IF NOT EXISTS embeddings (
8
- node_id TEXT PRIMARY KEY,
9
- vector BLOB NOT NULL,
10
- content_hash TEXT,
11
- created_at INTEGER DEFAULT (unixepoch())
12
- );
13
- `);
14
- return db;
15
- }
16
- describe('embedding stale detection', () => {
17
- it('stores content hash with embedding', () => {
18
- const db = makeDb();
19
- upsertEmbedding(db, 'n1', new Float32Array([0.1, 0.2]), 'hash_abc');
20
- const stored = getEmbeddingContentHash(db, 'n1');
21
- expect(stored).toBe('hash_abc');
22
- });
23
- it('isEmbeddingStale returns true when hash differs', () => {
24
- const db = makeDb();
25
- upsertEmbedding(db, 'n1', new Float32Array([0.1, 0.2]), 'old_hash');
26
- expect(isEmbeddingStale(db, 'n1', 'new_hash')).toBe(true);
27
- });
28
- it('isEmbeddingStale returns false when hash matches', () => {
29
- const db = makeDb();
30
- upsertEmbedding(db, 'n1', new Float32Array([0.1, 0.2]), 'same_hash');
31
- expect(isEmbeddingStale(db, 'n1', 'same_hash')).toBe(false);
32
- });
33
- it('isEmbeddingStale returns true when no embedding exists', () => {
34
- const db = makeDb();
35
- expect(isEmbeddingStale(db, 'n1', 'any_hash')).toBe(true);
36
- });
37
- });
38
- //# sourceMappingURL=embed-stale.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embed-stale.test.js","sourceRoot":"","sources":["../../../../src/__tests__/search/embed-stale.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAE9G,SAAS,MAAM;IACb,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC;IACpC,EAAE,CAAC,IAAI,CAAC;;;;;;;GAOP,CAAC,CAAC;IACH,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,uBAAuB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QACpE,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,eAAe,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;QACrE,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,MAAM,CAAC,gBAAgB,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=exact-search.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"exact-search.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/search/exact-search.test.ts"],"names":[],"mappings":""}
@@ -1,49 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import Database from 'better-sqlite3';
3
- import { exactVectorSearch } from '../../search/exact-search.js';
4
- function makeDb() {
5
- const db = new Database(':memory:');
6
- db.exec(`
7
- CREATE TABLE nodes (id TEXT PRIMARY KEY, label TEXT NOT NULL, name TEXT NOT NULL,
8
- norm_label TEXT, file_path TEXT, start_line INTEGER, end_line INTEGER,
9
- community_id INTEGER, is_exported INTEGER DEFAULT 0, language TEXT, properties TEXT);
10
- CREATE TABLE embeddings (node_id TEXT PRIMARY KEY, vector BLOB NOT NULL, content_hash TEXT, created_at INTEGER);
11
- `);
12
- db.prepare(`INSERT INTO nodes VALUES ('n1','Function','doAuth',null,'/auth.ts',1,5,null,1,null,null)`).run();
13
- db.prepare(`INSERT INTO nodes VALUES ('n2','Function','renderUI',null,'/ui.ts',1,3,null,1,null,null)`).run();
14
- // Store simple 3-dim embeddings: n1 ~ [1, 0, 0], n2 ~ [0, 1, 0]
15
- const v1 = new Float32Array([1, 0, 0]);
16
- const v2 = new Float32Array([0, 1, 0]);
17
- db.prepare(`INSERT INTO embeddings VALUES ('n1', ?, null, 0)`).run(Buffer.from(v1.buffer));
18
- db.prepare(`INSERT INTO embeddings VALUES ('n2', ?, null, 0)`).run(Buffer.from(v2.buffer));
19
- return db;
20
- }
21
- describe('exactVectorSearch', () => {
22
- it('returns closest node to query vector', () => {
23
- const db = makeDb();
24
- const query = new Float32Array([0.9, 0.1, 0]);
25
- const results = exactVectorSearch(db, query, { limit: 2 });
26
- expect(results[0]?.id).toBe('n1');
27
- });
28
- it('respects limit', () => {
29
- const db = makeDb();
30
- const query = new Float32Array([1, 0, 0]);
31
- const results = exactVectorSearch(db, query, { limit: 1 });
32
- expect(results).toHaveLength(1);
33
- });
34
- it('returns score between 0 and 1', () => {
35
- const db = makeDb();
36
- const query = new Float32Array([1, 0, 0]);
37
- const results = exactVectorSearch(db, query, { limit: 2 });
38
- expect(results[0].score).toBeGreaterThan(0);
39
- expect(results[0].score).toBeLessThanOrEqual(1);
40
- });
41
- it('returns empty array when no embeddings', () => {
42
- const db = new Database(':memory:');
43
- db.exec(`CREATE TABLE embeddings (node_id TEXT PRIMARY KEY, vector BLOB NOT NULL, content_hash TEXT, created_at INTEGER);
44
- CREATE TABLE nodes (id TEXT PRIMARY KEY, label TEXT NOT NULL, name TEXT NOT NULL, norm_label TEXT, file_path TEXT, start_line INTEGER, end_line INTEGER, community_id INTEGER, is_exported INTEGER DEFAULT 0, language TEXT, properties TEXT);`);
45
- const results = exactVectorSearch(db, new Float32Array([1, 0, 0]), { limit: 5 });
46
- expect(results).toHaveLength(0);
47
- });
48
- });
49
- //# sourceMappingURL=exact-search.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"exact-search.test.js","sourceRoot":"","sources":["../../../../src/__tests__/search/exact-search.test.ts"],"names":[],"mappings":"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"}
@@ -1,25 +0,0 @@
1
- /**
2
- * monograph_embed MCP tool
3
- *
4
- * Embeds all symbol nodes in the Monograph knowledge graph using
5
- * Snowflake/snowflake-arctic-embed-xs (384 dimensions).
6
- *
7
- * Requires @huggingface/transformers to be installed.
8
- * Returns { embedded, skipped, model } on success or an error message.
9
- */
10
- import type Database from 'better-sqlite3';
11
- export interface EmbedToolInput {
12
- codeOnly?: boolean;
13
- force?: boolean;
14
- }
15
- export interface EmbedToolResult {
16
- embedded: number;
17
- skipped: number;
18
- model: string;
19
- }
20
- /**
21
- * Run the embedding pipeline on the open database.
22
- * Exported so the CLI handler can call it after opening the DB.
23
- */
24
- export declare function runEmbed(db: Database.Database, options?: EmbedToolInput): Promise<EmbedToolResult>;
25
- //# sourceMappingURL=embed.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/embed.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAID;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,eAAe,CAAC,CAsB1B"}
@@ -1,33 +0,0 @@
1
- /**
2
- * monograph_embed MCP tool
3
- *
4
- * Embeds all symbol nodes in the Monograph knowledge graph using
5
- * Snowflake/snowflake-arctic-embed-xs (384 dimensions).
6
- *
7
- * Requires @huggingface/transformers to be installed.
8
- * Returns { embedded, skipped, model } on success or an error message.
9
- */
10
- const MODEL = 'Snowflake/snowflake-arctic-embed-xs';
11
- /**
12
- * Run the embedding pipeline on the open database.
13
- * Exported so the CLI handler can call it after opening the DB.
14
- */
15
- export async function runEmbed(db, options = {}) {
16
- const { force = false, codeOnly = false } = options;
17
- let getEmbedder;
18
- let embedAll;
19
- try {
20
- const embedderMod = await import('../search/embedder.js');
21
- const batchMod = await import('../search/embed-batch.js');
22
- getEmbedder = embedderMod.getEmbedder;
23
- embedAll = batchMod.embedAll;
24
- }
25
- catch {
26
- throw new Error('@huggingface/transformers is required for embedding. ' +
27
- 'Install it with: npm install @huggingface/transformers');
28
- }
29
- const embedder = await getEmbedder();
30
- const result = await embedAll(db, embedder, force, codeOnly);
31
- return { ...result, model: MODEL };
32
- }
33
- //# sourceMappingURL=embed.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embed.js","sourceRoot":"","sources":["../../../src/mcp-tools/embed.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,MAAM,KAAK,GAAG,qCAAqC,CAAC;AAEpD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,EAAqB,EACrB,UAA0B,EAAE;IAE5B,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,QAAQ,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAEpD,IAAI,WAAmC,CAAC;IACxC,IAAI,QAAkH,CAAC;IAEvH,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;QAC1D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;QAC1D,WAAW,GAAG,WAAW,CAAC,WAAW,CAAC;QACtC,QAAQ,GAAG,QAAQ,CAAC,QAA2B,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,uDAAuD;YACrD,wDAAwD,CAC3D,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAC;IACrC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,EAAE,QAA0C,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;IAE/F,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AACrC,CAAC"}
@@ -1,6 +0,0 @@
1
- import type { PipelinePhase } from '../types.js';
2
- export interface EmbeddingsOutput {
3
- embeddingsBuilt: number;
4
- }
5
- export declare const embeddingsPhase: PipelinePhase<EmbeddingsOutput>;
6
- //# sourceMappingURL=embeddings.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embeddings.d.ts","sourceRoot":"","sources":["../../../../src/pipeline/phases/embeddings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,aAAa,CAAC;AAIlE,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,gBAAgB,CAQ3D,CAAC"}
@@ -1,12 +0,0 @@
1
- // buildEmbeddings is not yet in embedding-store; phase is a no-op until it's added
2
- function buildEmbeddings(_db) { }
3
- export const embeddingsPhase = {
4
- name: 'embeddings',
5
- deps: ['communities', 'docs-parse', 'pdf-parse', 'contextual-proximity', 'llm-extract'],
6
- async execute(ctx) {
7
- buildEmbeddings(ctx.db);
8
- const row = ctx.db.prepare('SELECT COUNT(*) as n FROM nodes WHERE embedding IS NOT NULL').get();
9
- return { embeddingsBuilt: row.n };
10
- },
11
- };
12
- //# sourceMappingURL=embeddings.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../../../../src/pipeline/phases/embeddings.ts"],"names":[],"mappings":"AACA,mFAAmF;AACnF,SAAS,eAAe,CAAC,GAAY,IAA4B,CAAC;AAMlE,MAAM,CAAC,MAAM,eAAe,GAAoC;IAC9D,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,CAAC,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,sBAAsB,EAAE,aAAa,CAAC;IACvF,KAAK,CAAC,OAAO,CAAC,GAAoB;QAChC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxB,MAAM,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,6DAA6D,CAAC,CAAC,GAAG,EAAmB,CAAC;QACjH,OAAO,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;IACpC,CAAC;CACF,CAAC"}
@@ -1,16 +0,0 @@
1
- export type EmbedDevice = 'cpu' | 'cuda' | 'mps';
2
- export interface EmbedDeviceConfig {
3
- device?: EmbedDevice;
4
- threads?: number;
5
- }
6
- /**
7
- * Resolves the compute device to use for local embedders.
8
- *
9
- * Priority:
10
- * 1. Explicit `config.device` if provided
11
- * 2. 'mps' on macOS (darwin) when not explicitly overridden
12
- * 3. 'cuda' when CUDA_VISIBLE_DEVICES env var is set (non-empty)
13
- * 4. 'cpu' as fallback
14
- */
15
- export declare function resolveDevice(config?: EmbedDeviceConfig): EmbedDevice;
16
- //# sourceMappingURL=device-config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"device-config.d.ts","sourceRoot":"","sources":["../../../src/search/device-config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG,WAAW,CAcrE"}
@@ -1,22 +0,0 @@
1
- /**
2
- * Resolves the compute device to use for local embedders.
3
- *
4
- * Priority:
5
- * 1. Explicit `config.device` if provided
6
- * 2. 'mps' on macOS (darwin) when not explicitly overridden
7
- * 3. 'cuda' when CUDA_VISIBLE_DEVICES env var is set (non-empty)
8
- * 4. 'cpu' as fallback
9
- */
10
- export function resolveDevice(config) {
11
- if (config?.device) {
12
- return config.device;
13
- }
14
- if (process.platform === 'darwin') {
15
- return 'mps';
16
- }
17
- if (process.env['CUDA_VISIBLE_DEVICES']) {
18
- return 'cuda';
19
- }
20
- return 'cpu';
21
- }
22
- //# sourceMappingURL=device-config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"device-config.js","sourceRoot":"","sources":["../../../src/search/device-config.ts"],"names":[],"mappings":"AAOA;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,MAA0B;IACtD,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;QACnB,OAAO,MAAM,CAAC,MAAM,CAAC;IACvB,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACxC,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -1,30 +0,0 @@
1
- /**
2
- * Batch-embed all symbol nodes that don't yet have an embedding stored.
3
- */
4
- import type Database from 'better-sqlite3';
5
- import type { EmbedderFn } from './embedder.js';
6
- import type { EmbedDeviceConfig } from './device-config.js';
7
- import type { HttpEmbedderConfig } from './http-embedder.js';
8
- import { countEmbeddings } from '../storage/embedding-store.js';
9
- export interface EmbedBatchConfig {
10
- device?: EmbedDeviceConfig;
11
- remote?: HttpEmbedderConfig;
12
- batchSize?: number;
13
- }
14
- /**
15
- * Embed a list of text strings and return an array of embedding vectors.
16
- *
17
- * If `config.remote` is provided, delegates to `HttpEmbedder`.
18
- * Otherwise falls back to the local HuggingFace embedder.
19
- *
20
- * @param texts - Strings to embed
21
- * @param config - Optional device/remote/batchSize config
22
- */
23
- export declare function embedBatch(texts: string[], config?: EmbedBatchConfig): Promise<number[][]>;
24
- export interface EmbedAllResult {
25
- embedded: number;
26
- skipped: number;
27
- }
28
- export declare function embedAll(db: Database.Database, embedder: EmbedderFn, force?: boolean, codeOnly?: boolean): Promise<EmbedAllResult>;
29
- export { countEmbeddings };
30
- //# sourceMappingURL=embed-batch.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embed-batch.d.ts","sourceRoot":"","sources":["../../../src/search/embed-batch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAA0C,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAKxG,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CA6BrB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAcD,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,QAAQ,EAAE,UAAU,EACpB,KAAK,UAAQ,EACb,QAAQ,UAAQ,GACf,OAAO,CAAC,cAAc,CAAC,CAkDzB;AAgBD,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -1,99 +0,0 @@
1
- /**
2
- * Batch-embed all symbol nodes that don't yet have an embedding stored.
3
- */
4
- import { upsertEmbedding, ensureEmbeddingSchema, countEmbeddings } from '../storage/embedding-store.js';
5
- import { embedText } from './embedder.js';
6
- const BATCH_SIZE = 32;
7
- /**
8
- * Embed a list of text strings and return an array of embedding vectors.
9
- *
10
- * If `config.remote` is provided, delegates to `HttpEmbedder`.
11
- * Otherwise falls back to the local HuggingFace embedder.
12
- *
13
- * @param texts - Strings to embed
14
- * @param config - Optional device/remote/batchSize config
15
- */
16
- export async function embedBatch(texts, config) {
17
- // Store device config in a local variable (passed through for future use)
18
- const _device = config?.device;
19
- void _device;
20
- if (config?.remote) {
21
- const { HttpEmbedder } = await import('./http-embedder.js');
22
- const embedder = new HttpEmbedder(config.remote);
23
- return embedder.embedBatch(texts);
24
- }
25
- // Local embedder path
26
- const { getEmbedder } = await import('./embedder.js');
27
- const embedderFn = await getEmbedder();
28
- const batchSize = config?.batchSize ?? BATCH_SIZE;
29
- const results = [];
30
- for (let i = 0; i < texts.length; i += batchSize) {
31
- const batch = texts.slice(i, i + batchSize);
32
- const batchResults = await Promise.all(batch.map(async (text) => {
33
- const vector = await embedText(text, embedderFn);
34
- return Array.from(vector);
35
- }));
36
- results.push(...batchResults);
37
- }
38
- return results;
39
- }
40
- /**
41
- * Embed all nodes that are missing embeddings.
42
- *
43
- * Nodes are fetched in batches of BATCH_SIZE to avoid loading the entire DB
44
- * into memory at once.
45
- *
46
- * @param db - Open monograph database
47
- * @param embedder - Feature extraction pipeline function
48
- * @param force - When true, re-embed nodes even if they already have an embedding
49
- */
50
- const CODE_LABELS = new Set(['Function', 'Method', 'Class', 'Module', 'Interface', 'Enum', 'Struct', 'Constructor', 'Variable', 'Const', 'TypeAlias']);
51
- export async function embedAll(db, embedder, force = false, codeOnly = false) {
52
- let embedded = 0;
53
- let skipped = 0;
54
- // Fetch all node IDs and their name+normLabel (used as the text to embed)
55
- const rows = db
56
- .prepare(`SELECT id, name, norm_label, label, file_path
57
- FROM nodes
58
- ORDER BY rowid`)
59
- .all();
60
- const filteredRows = codeOnly ? rows.filter((r) => CODE_LABELS.has(r.label)) : rows;
61
- // Build set of node IDs that already have embeddings
62
- const existingIds = force
63
- ? new Set()
64
- : new Set(db.prepare('SELECT node_id FROM embeddings').all().map((r) => r.node_id));
65
- // Filter to only rows that need embedding
66
- const toEmbed = filteredRows.filter((r) => !existingIds.has(r.id));
67
- skipped = filteredRows.length - toEmbed.length;
68
- // Ensure schema migration runs once before any upsert — not per-row.
69
- if (toEmbed.length > 0)
70
- ensureEmbeddingSchema(db);
71
- // Process in batches
72
- for (let i = 0; i < toEmbed.length; i += BATCH_SIZE) {
73
- const batch = toEmbed.slice(i, i + BATCH_SIZE);
74
- await Promise.all(batch.map(async (row) => {
75
- const text = buildNodeText(row);
76
- try {
77
- const vector = await embedText(text, embedder);
78
- upsertEmbedding(db, row.id, vector);
79
- embedded++;
80
- }
81
- catch {
82
- // Skip nodes that fail to embed (e.g. empty name)
83
- skipped++;
84
- }
85
- }));
86
- }
87
- return { embedded, skipped };
88
- }
89
- // ── Helpers ───────────────────────────────────────────────────────────────────
90
- function buildNodeText(row) {
91
- const parts = [row.name, row.label];
92
- if (row.norm_label && row.norm_label !== row.name)
93
- parts.push(row.norm_label);
94
- if (row.file_path)
95
- parts.push(row.file_path);
96
- return parts.join(' ');
97
- }
98
- export { countEmbeddings };
99
- //# sourceMappingURL=embed-batch.js.map