@dreb/semantic-search 1.18.0
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/.claude-plugin/plugin.json +17 -0
- package/.mcp.json +8 -0
- package/README.md +97 -0
- package/bin/server.js +14 -0
- package/dist/chunker.d.ts +21 -0
- package/dist/chunker.d.ts.map +1 -0
- package/dist/chunker.js +51 -0
- package/dist/chunker.js.map +1 -0
- package/dist/db.d.ts +89 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +406 -0
- package/dist/db.js.map +1 -0
- package/dist/embedder.d.ts +52 -0
- package/dist/embedder.d.ts.map +1 -0
- package/dist/embedder.js +158 -0
- package/dist/embedder.js.map +1 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +37 -0
- package/dist/format.js.map +1 -0
- package/dist/index-manager.d.ts +55 -0
- package/dist/index-manager.d.ts.map +1 -0
- package/dist/index-manager.js +311 -0
- package/dist/index-manager.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +25 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +149 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/metrics/bm25.d.ts +10 -0
- package/dist/metrics/bm25.d.ts.map +1 -0
- package/dist/metrics/bm25.js +32 -0
- package/dist/metrics/bm25.js.map +1 -0
- package/dist/metrics/git-recency.d.ts +14 -0
- package/dist/metrics/git-recency.d.ts.map +1 -0
- package/dist/metrics/git-recency.js +123 -0
- package/dist/metrics/git-recency.js.map +1 -0
- package/dist/metrics/import-graph.d.ts +15 -0
- package/dist/metrics/import-graph.d.ts.map +1 -0
- package/dist/metrics/import-graph.js +115 -0
- package/dist/metrics/import-graph.js.map +1 -0
- package/dist/metrics/path-match.d.ts +13 -0
- package/dist/metrics/path-match.d.ts.map +1 -0
- package/dist/metrics/path-match.js +54 -0
- package/dist/metrics/path-match.js.map +1 -0
- package/dist/metrics/symbol-match.d.ts +12 -0
- package/dist/metrics/symbol-match.d.ts.map +1 -0
- package/dist/metrics/symbol-match.js +62 -0
- package/dist/metrics/symbol-match.js.map +1 -0
- package/dist/metrics/tokenize.d.ts +12 -0
- package/dist/metrics/tokenize.d.ts.map +1 -0
- package/dist/metrics/tokenize.js +29 -0
- package/dist/metrics/tokenize.js.map +1 -0
- package/dist/poem.d.ts +38 -0
- package/dist/poem.d.ts.map +1 -0
- package/dist/poem.js +214 -0
- package/dist/poem.js.map +1 -0
- package/dist/query-classifier.d.ts +17 -0
- package/dist/query-classifier.d.ts.map +1 -0
- package/dist/query-classifier.js +54 -0
- package/dist/query-classifier.js.map +1 -0
- package/dist/scanner.d.ts +30 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +343 -0
- package/dist/scanner.js.map +1 -0
- package/dist/search.d.ts +63 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +400 -0
- package/dist/search.js.map +1 -0
- package/dist/text-chunker.d.ts +15 -0
- package/dist/text-chunker.d.ts.map +1 -0
- package/dist/text-chunker.js +580 -0
- package/dist/text-chunker.js.map +1 -0
- package/dist/tree-sitter-chunker.d.ts +25 -0
- package/dist/tree-sitter-chunker.d.ts.map +1 -0
- package/dist/tree-sitter-chunker.js +357 -0
- package/dist/tree-sitter-chunker.js.map +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/vector-store.d.ts +43 -0
- package/dist/vector-store.d.ts.map +1 -0
- package/dist/vector-store.js +73 -0
- package/dist/vector-store.js.map +1 -0
- package/package.json +71 -0
- package/skills/search/SKILL.md +56 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-manager.js","sourceRoot":"","sources":["../src/index-manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Index lifecycle manager.\n *\n * Orchestrates: file scanning → chunking → embedding → FTS indexing → import graph.\n * Supports incremental updates via mtime comparison.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { chunkFile } from \"./chunker.js\";\nimport type { SearchDatabase } from \"./db.js\";\nimport { isSqliteAvailable, SearchDatabase as SearchDatabaseClass } from \"./db.js\";\nimport type { Embedder } from \"./embedder.js\";\nimport { type ScannedFile, scanProject } from \"./scanner.js\";\nimport type { IndexConfig, IndexedFile, IndexProgressCallback } from \"./types.js\";\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DB_FILENAME = \"search.db\";\n\n// ============================================================================\n// Index Manager\n// ============================================================================\n\nexport class IndexManager {\n\tprivate readonly config: IndexConfig;\n\tprivate db: SearchDatabase | null = null;\n\tprivate embedder: Embedder | null = null;\n\n\tconstructor(config: IndexConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/** Check if node:sqlite is available. */\n\tstatic isAvailable(): boolean {\n\t\treturn isSqliteAvailable();\n\t}\n\n\t/** Open or create the index database. */\n\topen(): SearchDatabase {\n\t\tif (this.db) return this.db;\n\n\t\tmkdirSync(this.config.indexDir, { recursive: true });\n\t\tconst dbPath = path.join(this.config.indexDir, DB_FILENAME);\n\t\tthis.db = new SearchDatabaseClass(dbPath);\n\t\treturn this.db;\n\t}\n\n\t/**\n\t * Set an external embedder instance to share with the caller.\n\t * When set, embedChunks() uses this instead of creating its own.\n\t */\n\tsetEmbedder(embedder: Embedder): void {\n\t\tthis.embedder = embedder;\n\t}\n\n\t/** Close the database and dispose resources. */\n\tclose(): void {\n\t\tif (this.db) {\n\t\t\tthis.db.close();\n\t\t\tthis.db = null;\n\t\t}\n\t\t// Only dispose the embedder if we own it (not shared externally).\n\t\t// The caller who set it via setEmbedder() is responsible for its lifecycle.\n\t\tthis.embedder = null;\n\t}\n\n\t/** Get the database, opening if needed. */\n\tgetDb(): SearchDatabase {\n\t\tif (!this.db) return this.open();\n\t\treturn this.db;\n\t}\n\n\t/**\n\t * Build or incrementally update the index.\n\t *\n\t * 1. Scan project for files\n\t * 2. Compare mtimes against stored records\n\t * 3. Re-chunk and re-embed only changed/new files\n\t * 4. Remove deleted files\n\t */\n\tasync buildIndex(\n\t\tonProgress?: IndexProgressCallback,\n\t): Promise<{ added: number; updated: number; removed: number; failed: number }> {\n\t\tconst db = this.getDb();\n\t\tconst config = this.config;\n\n\t\t// Phase 1: Scan\n\t\tonProgress?.(\"scanning\", 0, 1);\n\t\tconst scannedFiles = await scanProject(config.projectRoot, config.globalMemoryDir, config.visibleDirs);\n\t\tonProgress?.(\"scanning\", 1, 1);\n\n\t\t// Phase 2: Diff against existing index\n\t\tconst existingFiles = db.getAllFiles();\n\t\tconst existingByPath = new Map<string, IndexedFile>();\n\t\tfor (const f of existingFiles) {\n\t\t\texistingByPath.set(f.filePath, f);\n\t\t}\n\n\t\tconst scannedByPath = new Map<string, ScannedFile>();\n\t\tfor (const f of scannedFiles) {\n\t\t\tscannedByPath.set(f.filePath, f);\n\t\t}\n\n\t\tconst toAdd: ScannedFile[] = [];\n\t\tconst toUpdate: ScannedFile[] = [];\n\t\tconst toRemove: IndexedFile[] = [];\n\n\t\t// Find new and changed files\n\t\tfor (const scanned of scannedFiles) {\n\t\t\tconst existing = existingByPath.get(scanned.filePath);\n\t\t\tif (!existing) {\n\t\t\t\ttoAdd.push(scanned);\n\t\t\t} else if (existing.mtime !== scanned.mtime) {\n\t\t\t\ttoUpdate.push(scanned);\n\t\t\t}\n\t\t}\n\n\t\t// Find deleted files\n\t\tfor (const existing of existingFiles) {\n\t\t\tif (!scannedByPath.has(existing.filePath)) {\n\t\t\t\ttoRemove.push(existing);\n\t\t\t}\n\t\t}\n\n\t\tconst totalWork = toAdd.length + toUpdate.length + toRemove.length;\n\t\tif (totalWork === 0) {\n\t\t\treturn { added: 0, updated: 0, removed: 0, failed: 0 };\n\t\t}\n\n\t\t// Phase 3: Remove deleted files\n\t\tfor (const file of toRemove) {\n\t\t\tdb.deleteFile(file.id);\n\t\t}\n\n\t\t// Phase 4: Process new and changed files\n\t\tconst filesToProcess = [...toAdd, ...toUpdate];\n\t\tconst allNewChunkIds: number[] = [];\n\t\tlet failed = 0;\n\n\t\tfor (let i = 0; i < filesToProcess.length; i++) {\n\t\t\tconst scanned = filesToProcess[i];\n\t\t\tonProgress?.(\"indexing\", i + 1, filesToProcess.length);\n\n\t\t\ttry {\n\t\t\t\t// Read file content and chunk BEFORE the transaction so that\n\t\t\t\t// failures in I/O or chunking don't leave a committed mtime\n\t\t\t\t// with zero chunks (which would make the file permanently invisible).\n\t\t\t\tconst absPath = scanned.filePath.startsWith(\"~memory/\")\n\t\t\t\t\t? path.join(this.config.globalMemoryDir ?? \"\", scanned.filePath.replace(\"~memory/\", \"\"))\n\t\t\t\t\t: path.join(config.projectRoot, scanned.filePath);\n\n\t\t\t\tconst content = readFileSync(absPath, \"utf-8\");\n\t\t\t\tconst chunks = await chunkFile(content, scanned.filePath, scanned.fileType);\n\t\t\t\tconst imports = extractImports(content, scanned.filePath, scanned.fileType);\n\n\t\t\t\t// All DB mutations in a single transaction — atomic per file\n\t\t\t\tdb.transaction(() => {\n\t\t\t\t\tconst fileId = db.upsertFile(scanned.filePath, scanned.mtime, scanned.fileType);\n\n\t\t\t\t\t// Delete old chunks (for updates)\n\t\t\t\t\tconst existingFile = existingByPath.get(scanned.filePath);\n\t\t\t\t\tif (existingFile) {\n\t\t\t\t\t\tdb.deleteChunksForFile(existingFile.id);\n\t\t\t\t\t\tdb.deleteImportsForFile(existingFile.id);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Insert chunks and symbols\n\t\t\t\t\tfor (const chunk of chunks) {\n\t\t\t\t\t\tconst chunkId = db.insertChunk(\n\t\t\t\t\t\t\tfileId,\n\t\t\t\t\t\t\tchunk.filePath,\n\t\t\t\t\t\t\tchunk.startLine,\n\t\t\t\t\t\t\tchunk.endLine,\n\t\t\t\t\t\t\tchunk.kind,\n\t\t\t\t\t\t\tchunk.name,\n\t\t\t\t\t\t\tchunk.content,\n\t\t\t\t\t\t\tchunk.fileType,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tallNewChunkIds.push(chunkId);\n\n\t\t\t\t\t\tif (chunk.name) {\n\t\t\t\t\t\t\tdb.insertSymbol(chunkId, chunk.name, chunk.kind);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Store import edges\n\t\t\t\t\tfor (const imp of imports) {\n\t\t\t\t\t\tdb.insertImport(fileId, imp);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} catch (err: unknown) {\n\t\t\t\t// Re-throw DB-level errors (SQLITE_FULL, disk full, etc.) — these\n\t\t\t\t// indicate infrastructure problems, not per-file issues.\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\tif (message.includes(\"SQLITE\")) {\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\t// Skip files that fail to process (permissions, encoding issues, etc.)\n\t\t\t\tfailed++;\n\t\t\t}\n\t\t}\n\n\t\t// Phase 5: Embed new chunks\n\t\tif (allNewChunkIds.length > 0) {\n\t\t\tawait this.embedChunks(db, allNewChunkIds, onProgress);\n\t\t}\n\n\t\treturn { added: toAdd.length, updated: toUpdate.length, removed: toRemove.length, failed };\n\t}\n\n\t/**\n\t * Ensure all chunks have embeddings, generating any missing ones.\n\t */\n\tasync ensureEmbeddings(onProgress?: IndexProgressCallback): Promise<number> {\n\t\tconst db = this.getDb();\n\t\tconst missingIds = db.getChunkIdsWithoutEmbedding(this.config.modelName);\n\t\tif (missingIds.length === 0) return 0;\n\n\t\tawait this.embedChunks(db, missingIds, onProgress);\n\t\treturn missingIds.length;\n\t}\n\n\t/**\n\t * Generate embeddings for specific chunks.\n\t * Requires an embedder to be set via setEmbedder() before calling.\n\t */\n\tprivate async embedChunks(\n\t\tdb: SearchDatabase,\n\t\tchunkIds: number[],\n\t\tonProgress?: IndexProgressCallback,\n\t): Promise<void> {\n\t\tif (chunkIds.length === 0) return;\n\n\t\tif (!this.embedder) {\n\t\t\tthrow new Error(\"IndexManager: embedder not set. Call setEmbedder() before embedding.\");\n\t\t}\n\n\t\t// Get chunk contents\n\t\tconst chunks = db.getChunksById(chunkIds);\n\t\tconst texts = chunks.map((c) => c.content);\n\n\t\t// Generate embeddings\n\t\tconst vectors = await this.embedder.embedDocuments(texts, onProgress);\n\n\t\t// Store embeddings\n\t\tconst items = chunks.map((chunk, i) => ({\n\t\t\tchunkId: chunk.id,\n\t\t\tmodelName: this.config.modelName,\n\t\t\tvector: vectors[i],\n\t\t}));\n\t\tdb.batchUpsertEmbeddings(items);\n\t}\n\n\t/** Check if the index exists. */\n\tindexExists(): boolean {\n\t\tconst dbPath = path.join(this.config.indexDir, DB_FILENAME);\n\t\treturn existsSync(dbPath);\n\t}\n\n\t/** Get index stats. */\n\tgetStats(): { files: number; chunks: number } | null {\n\t\tif (!this.indexExists()) return null;\n\t\tconst db = this.getDb();\n\t\treturn {\n\t\t\tfiles: db.getFileCount(),\n\t\t\tchunks: db.getChunkCount(),\n\t\t};\n\t}\n}\n\n// ============================================================================\n// Import Extraction (simple regex-based)\n// ============================================================================\n\n/**\n * Extract import targets from source code.\n * Returns resolved relative paths (without extensions for JS/TS).\n */\nfunction extractImports(content: string, filePath: string, fileType: string): string[] {\n\tconst imports: string[] = [];\n\tconst dir = path.dirname(filePath);\n\n\tif (fileType === \"typescript\" || fileType === \"tsx\" || fileType === \"javascript\") {\n\t\t// ES6 imports: import ... from '...'\n\t\t// require(): require('...')\n\t\tconst importRe = /(?:import\\s+.*?\\s+from\\s+|import\\s*\\(|require\\s*\\()\\s*['\"]([^'\"]+)['\"]/g;\n\t\tfor (const match of content.matchAll(importRe)) {\n\t\t\tconst target = match[1];\n\t\t\tif (target.startsWith(\".\")) {\n\t\t\t\timports.push(resolveImportPath(dir, target));\n\t\t\t}\n\t\t}\n\t} else if (fileType === \"python\") {\n\t\t// from . import X, from .module import X\n\t\tconst fromRe = /from\\s+(\\.\\S*)\\s+import/g;\n\t\tfor (const match of content.matchAll(fromRe)) {\n\t\t\tconst resolved = resolvePythonImport(dir, match[1]);\n\t\t\tif (resolved) imports.push(resolved);\n\t\t}\n\t} else if (fileType === \"go\") {\n\t\t// import \"...\" or import ( \"...\" )\n\t\tconst importRe = /import\\s+(?:\\(\\s*(?:[\\s\\S]*?)\\s*\\)|\"([^\"]+)\")/g;\n\t\tconst pathRe = /\"([^\"]+)\"/g;\n\t\tfor (const match of content.matchAll(importRe)) {\n\t\t\tif (match[1]) {\n\t\t\t\timports.push(match[1]);\n\t\t\t} else {\n\t\t\t\t// Multi-line import block\n\t\t\t\tfor (const inner of match[0].matchAll(pathRe)) {\n\t\t\t\t\timports.push(inner[1]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if (fileType === \"rust\") {\n\t\t// use crate::..., use super::...,\n\t\tconst useRe = /use\\s+((?:crate|super|self)::[\\w:]+)/g;\n\t\tfor (const match of content.matchAll(useRe)) {\n\t\t\timports.push(match[1].replace(/::/g, \"/\"));\n\t\t}\n\t} else if (fileType === \"java\") {\n\t\t// import com.example.Foo;\n\t\tconst importRe = /import\\s+([\\w.]+)\\s*;/g;\n\t\tfor (const match of content.matchAll(importRe)) {\n\t\t\timports.push(match[1].replace(/\\./g, \"/\"));\n\t\t}\n\t} else if (fileType === \"c\" || fileType === \"cpp\") {\n\t\t// #include \"local.h\"\n\t\tconst includeRe = /#include\\s+\"([^\"]+)\"/g;\n\t\tfor (const match of content.matchAll(includeRe)) {\n\t\t\timports.push(path.posix.join(dir, match[1]));\n\t\t}\n\t}\n\n\treturn imports;\n}\n\n/** Resolve a relative JS/TS import to a file path. */\nfunction resolveImportPath(fromDir: string, importPath: string): string {\n\t// Strip .js/.ts extension if present (normalize)\n\tconst cleaned = importPath.replace(/\\.[jt]sx?$/, \"\");\n\treturn path.posix.join(fromDir, cleaned);\n}\n\n/** Resolve a Python relative import. */\nfunction resolvePythonImport(fromDir: string, importPath: string): string | null {\n\tif (importPath === \".\") return fromDir;\n\tconst dots = importPath.match(/^(\\.+)/);\n\tif (!dots) return null;\n\tconst levels = dots[1].length - 1;\n\tconst remainder = importPath.slice(dots[1].length).replace(/\\./g, \"/\");\n\tlet base = fromDir;\n\tfor (let i = 0; i < levels; i++) {\n\t\tbase = path.posix.dirname(base);\n\t}\n\treturn remainder ? path.posix.join(base, remainder) : base;\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { chunkFile } from "./chunker.js";
|
|
2
|
+
export { isSqliteAvailable, SearchDatabase } from "./db.js";
|
|
3
|
+
export type { EmbedderOptions } from "./embedder.js";
|
|
4
|
+
export { Embedder } from "./embedder.js";
|
|
5
|
+
export { formatResults } from "./format.js";
|
|
6
|
+
export { IndexManager } from "./index-manager.js";
|
|
7
|
+
export type { RankedCandidate } from "./poem.js";
|
|
8
|
+
export { poemRank } from "./poem.js";
|
|
9
|
+
export type { QueryType } from "./query-classifier.js";
|
|
10
|
+
export { classifyQuery } from "./query-classifier.js";
|
|
11
|
+
export type { ScannedFile } from "./scanner.js";
|
|
12
|
+
export { detectFileType, scanProject } from "./scanner.js";
|
|
13
|
+
export type { SearchEngineOptions, SearchOptions } from "./search.js";
|
|
14
|
+
export { SearchEngine } from "./search.js";
|
|
15
|
+
export type { Chunk, ChunkKind, FileType, ImportEdge, IndexConfig, IndexedFile, IndexProgressCallback, MetricName, MetricScores, SearchResult, StoredChunk, StoredEmbedding, TextFileType, TreeSitterLanguage, } from "./types.js";
|
|
16
|
+
export { METRIC_NAMES } from "./types.js";
|
|
17
|
+
export { cosineSimilarity, packVector, topKSimilar, unpackVector } from "./vector-store.js";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC5D,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3D,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,YAAY,EACX,KAAK,EACL,SAAS,EACT,QAAQ,EACR,UAAU,EACV,WAAW,EACX,WAAW,EACX,qBAAqB,EACrB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,eAAe,EACf,YAAY,EACZ,kBAAkB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["// Core API\n\n// Chunking\nexport { chunkFile } from \"./chunker.js\";\n\n// Database\nexport { isSqliteAvailable, SearchDatabase } from \"./db.js\";\nexport type { EmbedderOptions } from \"./embedder.js\";\n\n// Embedder\nexport { Embedder } from \"./embedder.js\";\n// Result formatting\nexport { formatResults } from \"./format.js\";\n// Index management\nexport { IndexManager } from \"./index-manager.js\";\nexport type { RankedCandidate } from \"./poem.js\";\n// Ranking\nexport { poemRank } from \"./poem.js\";\nexport type { QueryType } from \"./query-classifier.js\";\nexport { classifyQuery } from \"./query-classifier.js\";\nexport type { ScannedFile } from \"./scanner.js\";\n// Scanner\nexport { detectFileType, scanProject } from \"./scanner.js\";\nexport type { SearchEngineOptions, SearchOptions } from \"./search.js\";\nexport { SearchEngine } from \"./search.js\";\n// Types\nexport type {\n\tChunk,\n\tChunkKind,\n\tFileType,\n\tImportEdge,\n\tIndexConfig,\n\tIndexedFile,\n\tIndexProgressCallback,\n\tMetricName,\n\tMetricScores,\n\tSearchResult,\n\tStoredChunk,\n\tStoredEmbedding,\n\tTextFileType,\n\tTreeSitterLanguage,\n} from \"./types.js\";\nexport { METRIC_NAMES } from \"./types.js\";\n// Vector operations\nexport { cosineSimilarity, packVector, topKSimilar, unpackVector } from \"./vector-store.js\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Core API
|
|
2
|
+
// Chunking
|
|
3
|
+
export { chunkFile } from "./chunker.js";
|
|
4
|
+
// Database
|
|
5
|
+
export { isSqliteAvailable, SearchDatabase } from "./db.js";
|
|
6
|
+
// Embedder
|
|
7
|
+
export { Embedder } from "./embedder.js";
|
|
8
|
+
// Result formatting
|
|
9
|
+
export { formatResults } from "./format.js";
|
|
10
|
+
// Index management
|
|
11
|
+
export { IndexManager } from "./index-manager.js";
|
|
12
|
+
// Ranking
|
|
13
|
+
export { poemRank } from "./poem.js";
|
|
14
|
+
export { classifyQuery } from "./query-classifier.js";
|
|
15
|
+
// Scanner
|
|
16
|
+
export { detectFileType, scanProject } from "./scanner.js";
|
|
17
|
+
export { SearchEngine } from "./search.js";
|
|
18
|
+
export { METRIC_NAMES } from "./types.js";
|
|
19
|
+
// Vector operations
|
|
20
|
+
export { cosineSimilarity, packVector, topKSimilar, unpackVector } from "./vector-store.js";
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AAEX,WAAW;AACX,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,WAAW;AACX,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG5D,WAAW;AACX,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,oBAAoB;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,UAAU;AACV,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtD,UAAU;AACV,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAkB3C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,oBAAoB;AACpB,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["// Core API\n\n// Chunking\nexport { chunkFile } from \"./chunker.js\";\n\n// Database\nexport { isSqliteAvailable, SearchDatabase } from \"./db.js\";\nexport type { EmbedderOptions } from \"./embedder.js\";\n\n// Embedder\nexport { Embedder } from \"./embedder.js\";\n// Result formatting\nexport { formatResults } from \"./format.js\";\n// Index management\nexport { IndexManager } from \"./index-manager.js\";\nexport type { RankedCandidate } from \"./poem.js\";\n// Ranking\nexport { poemRank } from \"./poem.js\";\nexport type { QueryType } from \"./query-classifier.js\";\nexport { classifyQuery } from \"./query-classifier.js\";\nexport type { ScannedFile } from \"./scanner.js\";\n// Scanner\nexport { detectFileType, scanProject } from \"./scanner.js\";\nexport type { SearchEngineOptions, SearchOptions } from \"./search.js\";\nexport { SearchEngine } from \"./search.js\";\n// Types\nexport type {\n\tChunk,\n\tChunkKind,\n\tFileType,\n\tImportEdge,\n\tIndexConfig,\n\tIndexedFile,\n\tIndexProgressCallback,\n\tMetricName,\n\tMetricScores,\n\tSearchResult,\n\tStoredChunk,\n\tStoredEmbedding,\n\tTextFileType,\n\tTreeSitterLanguage,\n} from \"./types.js\";\nexport { METRIC_NAMES } from \"./types.js\";\n// Vector operations\nexport { cosineSimilarity, packVector, topKSimilar, unpackVector } from \"./vector-store.js\";\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP stdio server adapter for semantic codebase search.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the SearchEngine as a single "search" tool over the Model Context Protocol,
|
|
5
|
+
* enabling any MCP-compatible client to run semantic codebase queries.
|
|
6
|
+
*
|
|
7
|
+
* The server defaults to using its CWD as the project directory. Claude Code
|
|
8
|
+
* launches MCP servers with CWD set to the project root, so no configuration
|
|
9
|
+
* is needed for typical per-project usage.
|
|
10
|
+
*/
|
|
11
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
12
|
+
/**
|
|
13
|
+
* Create an MCP server instance configured with the semantic search tool.
|
|
14
|
+
*
|
|
15
|
+
* @param defaultProjectDir - Default project directory for searches. Used when
|
|
16
|
+
* the client doesn't specify `projectDir` in the tool call. Typically the
|
|
17
|
+
* server's CWD, which Claude Code sets to the project root.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createMcpServer(defaultProjectDir: string): Server;
|
|
20
|
+
/**
|
|
21
|
+
* Create and start an MCP server over stdio.
|
|
22
|
+
* This blocks until the transport is closed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function startServer(projectDir: string): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=mcp-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAqDnE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CA6FjE;AAMD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAInE","sourcesContent":["/**\n * MCP stdio server adapter for semantic codebase search.\n *\n * Exposes the SearchEngine as a single \"search\" tool over the Model Context Protocol,\n * enabling any MCP-compatible client to run semantic codebase queries.\n *\n * The server defaults to using its CWD as the project directory. Claude Code\n * launches MCP servers with CWD set to the project root, so no configuration\n * is needed for typical per-project usage.\n */\n\nimport { createRequire } from \"node:module\";\nimport { resolve } from \"node:path\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, type CallToolResult, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { formatResults } from \"./format.js\";\nimport { SearchEngine } from \"./search.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version: packageVersion } = require(\"../package.json\") as { version: string };\n\n// ============================================================================\n// Tool Schema (JSON Schema for MCP)\n// ============================================================================\n\nconst SEARCH_TOOL = {\n\tname: \"search\",\n\tdescription:\n\t\t\"Search the codebase using natural language queries. Returns ranked code/doc results using semantic similarity and keyword matching. First query builds the index (may take a moment); subsequent queries are fast.\",\n\tinputSchema: {\n\t\ttype: \"object\" as const,\n\t\tproperties: {\n\t\t\tquery: { type: \"string\", description: \"Search query (natural language, identifier, or path)\" },\n\t\t\tprojectDir: {\n\t\t\t\ttype: \"string\",\n\t\t\t\tdescription: \"Absolute path to the project directory to search. Use your current working directory.\",\n\t\t\t},\n\t\t\tpath: { type: \"string\", description: \"Restrict search to files under this path\" },\n\t\t\tlimit: { type: \"number\", description: \"Maximum results to return (default: 20)\" },\n\t\t\trebuild: { type: \"boolean\", description: \"Force index rebuild (default: false)\" },\n\t\t},\n\t\trequired: [\"query\", \"projectDir\"],\n\t},\n};\n\n// ============================================================================\n// Engine Cache\n// ============================================================================\n\n/** Cache search engines per project root to reuse index across calls. */\nconst engineCache = new Map<string, SearchEngine>();\n\nfunction getSearchEngine(projectRoot: string): SearchEngine {\n\tlet engine = engineCache.get(projectRoot);\n\tif (!engine) {\n\t\tengine = new SearchEngine(projectRoot);\n\t\tengineCache.set(projectRoot, engine);\n\t}\n\treturn engine;\n}\n\n// ============================================================================\n// Server Factory\n// ============================================================================\n\n/**\n * Create an MCP server instance configured with the semantic search tool.\n *\n * @param defaultProjectDir - Default project directory for searches. Used when\n * the client doesn't specify `projectDir` in the tool call. Typically the\n * server's CWD, which Claude Code sets to the project root.\n */\nexport function createMcpServer(defaultProjectDir: string): Server {\n\tconst server = new Server(\n\t\t{ name: \"semantic-search\", version: packageVersion },\n\t\t{ capabilities: { tools: {}, logging: {} } },\n\t);\n\n\tserver.setRequestHandler(ListToolsRequestSchema, async () => ({\n\t\ttools: [SEARCH_TOOL],\n\t}));\n\n\tserver.setRequestHandler(CallToolRequestSchema, async (request): Promise<CallToolResult> => {\n\t\tif (request.params.name !== \"search\") {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `Unknown tool: ${request.params.name}` }],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\n\t\tconst args = (request.params.arguments ?? {}) as {\n\t\t\tquery?: string;\n\t\t\tprojectDir?: string;\n\t\t\tpath?: string;\n\t\t\tlimit?: number;\n\t\t\trebuild?: boolean;\n\t\t};\n\t\tconst { query, path: searchPath, rebuild = false } = args;\n\t\tconst limit = typeof args.limit === \"number\" && args.limit > 0 ? Math.floor(args.limit) : 20;\n\n\t\tconst projectDir = args.projectDir ? resolve(args.projectDir) : defaultProjectDir;\n\n\t\tif (!SearchEngine.isAvailable()) {\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: \"Semantic search requires Node.js 22+ (for built-in SQLite). Current version does not support node:sqlite.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\n\t\tif (!query || query.trim().length === 0) {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: \"Search query cannot be empty.\" }],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\tconst engine = getSearchEngine(projectDir);\n\n\t\t\tif (rebuild) {\n\t\t\t\tawait engine.resetIndex();\n\t\t\t}\n\n\t\t\t// Send progress via logging messages\n\t\t\tconst results = await engine.search(query, {\n\t\t\t\tlimit,\n\t\t\t\tpathFilter: searchPath,\n\t\t\t\tonProgress: (phase, current, total) => {\n\t\t\t\t\tserver\n\t\t\t\t\t\t.sendLoggingMessage({\n\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\tlogger: \"semantic-search\",\n\t\t\t\t\t\t\tdata: `${phase}: ${current}/${total}`,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\t\t// Ignore errors sending progress — client may not support logging\n\t\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst text = formatResults(results);\n\t\t\tconst stats = engine.getStats();\n\n\t\t\tlet statsLine = \"\";\n\t\t\tif (stats) {\n\t\t\t\tstatsLine = `\\n\\n[Index: ${stats.files} files, ${stats.chunks} chunks]`;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: text + statsLine }],\n\t\t\t};\n\t\t} catch (err) {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `Search failed: ${err instanceof Error ? err.message : String(err)}` }],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\t});\n\n\treturn server;\n}\n\n// ============================================================================\n// Server Startup\n// ============================================================================\n\n/**\n * Create and start an MCP server over stdio.\n * This blocks until the transport is closed.\n */\nexport async function startServer(projectDir: string): Promise<void> {\n\tconst server = createMcpServer(projectDir);\n\tconst transport = new StdioServerTransport();\n\tawait server.connect(transport);\n}\n"]}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP stdio server adapter for semantic codebase search.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the SearchEngine as a single "search" tool over the Model Context Protocol,
|
|
5
|
+
* enabling any MCP-compatible client to run semantic codebase queries.
|
|
6
|
+
*
|
|
7
|
+
* The server defaults to using its CWD as the project directory. Claude Code
|
|
8
|
+
* launches MCP servers with CWD set to the project root, so no configuration
|
|
9
|
+
* is needed for typical per-project usage.
|
|
10
|
+
*/
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
import { resolve } from "node:path";
|
|
13
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
+
import { formatResults } from "./format.js";
|
|
17
|
+
import { SearchEngine } from "./search.js";
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const { version: packageVersion } = require("../package.json");
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Tool Schema (JSON Schema for MCP)
|
|
22
|
+
// ============================================================================
|
|
23
|
+
const SEARCH_TOOL = {
|
|
24
|
+
name: "search",
|
|
25
|
+
description: "Search the codebase using natural language queries. Returns ranked code/doc results using semantic similarity and keyword matching. First query builds the index (may take a moment); subsequent queries are fast.",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
query: { type: "string", description: "Search query (natural language, identifier, or path)" },
|
|
30
|
+
projectDir: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "Absolute path to the project directory to search. Use your current working directory.",
|
|
33
|
+
},
|
|
34
|
+
path: { type: "string", description: "Restrict search to files under this path" },
|
|
35
|
+
limit: { type: "number", description: "Maximum results to return (default: 20)" },
|
|
36
|
+
rebuild: { type: "boolean", description: "Force index rebuild (default: false)" },
|
|
37
|
+
},
|
|
38
|
+
required: ["query", "projectDir"],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Engine Cache
|
|
43
|
+
// ============================================================================
|
|
44
|
+
/** Cache search engines per project root to reuse index across calls. */
|
|
45
|
+
const engineCache = new Map();
|
|
46
|
+
function getSearchEngine(projectRoot) {
|
|
47
|
+
let engine = engineCache.get(projectRoot);
|
|
48
|
+
if (!engine) {
|
|
49
|
+
engine = new SearchEngine(projectRoot);
|
|
50
|
+
engineCache.set(projectRoot, engine);
|
|
51
|
+
}
|
|
52
|
+
return engine;
|
|
53
|
+
}
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Server Factory
|
|
56
|
+
// ============================================================================
|
|
57
|
+
/**
|
|
58
|
+
* Create an MCP server instance configured with the semantic search tool.
|
|
59
|
+
*
|
|
60
|
+
* @param defaultProjectDir - Default project directory for searches. Used when
|
|
61
|
+
* the client doesn't specify `projectDir` in the tool call. Typically the
|
|
62
|
+
* server's CWD, which Claude Code sets to the project root.
|
|
63
|
+
*/
|
|
64
|
+
export function createMcpServer(defaultProjectDir) {
|
|
65
|
+
const server = new Server({ name: "semantic-search", version: packageVersion }, { capabilities: { tools: {}, logging: {} } });
|
|
66
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
67
|
+
tools: [SEARCH_TOOL],
|
|
68
|
+
}));
|
|
69
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
70
|
+
if (request.params.name !== "search") {
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const args = (request.params.arguments ?? {});
|
|
77
|
+
const { query, path: searchPath, rebuild = false } = args;
|
|
78
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 20;
|
|
79
|
+
const projectDir = args.projectDir ? resolve(args.projectDir) : defaultProjectDir;
|
|
80
|
+
if (!SearchEngine.isAvailable()) {
|
|
81
|
+
return {
|
|
82
|
+
content: [
|
|
83
|
+
{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: "Semantic search requires Node.js 22+ (for built-in SQLite). Current version does not support node:sqlite.",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (!query || query.trim().length === 0) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: "text", text: "Search query cannot be empty." }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const engine = getSearchEngine(projectDir);
|
|
99
|
+
if (rebuild) {
|
|
100
|
+
await engine.resetIndex();
|
|
101
|
+
}
|
|
102
|
+
// Send progress via logging messages
|
|
103
|
+
const results = await engine.search(query, {
|
|
104
|
+
limit,
|
|
105
|
+
pathFilter: searchPath,
|
|
106
|
+
onProgress: (phase, current, total) => {
|
|
107
|
+
server
|
|
108
|
+
.sendLoggingMessage({
|
|
109
|
+
level: "info",
|
|
110
|
+
logger: "semantic-search",
|
|
111
|
+
data: `${phase}: ${current}/${total}`,
|
|
112
|
+
})
|
|
113
|
+
.catch(() => {
|
|
114
|
+
// Ignore errors sending progress — client may not support logging
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const text = formatResults(results);
|
|
119
|
+
const stats = engine.getStats();
|
|
120
|
+
let statsLine = "";
|
|
121
|
+
if (stats) {
|
|
122
|
+
statsLine = `\n\n[Index: ${stats.files} files, ${stats.chunks} chunks]`;
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
content: [{ type: "text", text: text + statsLine }],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
return {
|
|
130
|
+
content: [{ type: "text", text: `Search failed: ${err instanceof Error ? err.message : String(err)}` }],
|
|
131
|
+
isError: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return server;
|
|
136
|
+
}
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Server Startup
|
|
139
|
+
// ============================================================================
|
|
140
|
+
/**
|
|
141
|
+
* Create and start an MCP server over stdio.
|
|
142
|
+
* This blocks until the transport is closed.
|
|
143
|
+
*/
|
|
144
|
+
export async function startServer(projectDir) {
|
|
145
|
+
const server = createMcpServer(projectDir);
|
|
146
|
+
const transport = new StdioServerTransport();
|
|
147
|
+
await server.connect(transport);
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=mcp-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP stdio server adapter for semantic codebase search.\n *\n * Exposes the SearchEngine as a single \"search\" tool over the Model Context Protocol,\n * enabling any MCP-compatible client to run semantic codebase queries.\n *\n * The server defaults to using its CWD as the project directory. Claude Code\n * launches MCP servers with CWD set to the project root, so no configuration\n * is needed for typical per-project usage.\n */\n\nimport { createRequire } from \"node:module\";\nimport { resolve } from \"node:path\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, type CallToolResult, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { formatResults } from \"./format.js\";\nimport { SearchEngine } from \"./search.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version: packageVersion } = require(\"../package.json\") as { version: string };\n\n// ============================================================================\n// Tool Schema (JSON Schema for MCP)\n// ============================================================================\n\nconst SEARCH_TOOL = {\n\tname: \"search\",\n\tdescription:\n\t\t\"Search the codebase using natural language queries. Returns ranked code/doc results using semantic similarity and keyword matching. First query builds the index (may take a moment); subsequent queries are fast.\",\n\tinputSchema: {\n\t\ttype: \"object\" as const,\n\t\tproperties: {\n\t\t\tquery: { type: \"string\", description: \"Search query (natural language, identifier, or path)\" },\n\t\t\tprojectDir: {\n\t\t\t\ttype: \"string\",\n\t\t\t\tdescription: \"Absolute path to the project directory to search. Use your current working directory.\",\n\t\t\t},\n\t\t\tpath: { type: \"string\", description: \"Restrict search to files under this path\" },\n\t\t\tlimit: { type: \"number\", description: \"Maximum results to return (default: 20)\" },\n\t\t\trebuild: { type: \"boolean\", description: \"Force index rebuild (default: false)\" },\n\t\t},\n\t\trequired: [\"query\", \"projectDir\"],\n\t},\n};\n\n// ============================================================================\n// Engine Cache\n// ============================================================================\n\n/** Cache search engines per project root to reuse index across calls. */\nconst engineCache = new Map<string, SearchEngine>();\n\nfunction getSearchEngine(projectRoot: string): SearchEngine {\n\tlet engine = engineCache.get(projectRoot);\n\tif (!engine) {\n\t\tengine = new SearchEngine(projectRoot);\n\t\tengineCache.set(projectRoot, engine);\n\t}\n\treturn engine;\n}\n\n// ============================================================================\n// Server Factory\n// ============================================================================\n\n/**\n * Create an MCP server instance configured with the semantic search tool.\n *\n * @param defaultProjectDir - Default project directory for searches. Used when\n * the client doesn't specify `projectDir` in the tool call. Typically the\n * server's CWD, which Claude Code sets to the project root.\n */\nexport function createMcpServer(defaultProjectDir: string): Server {\n\tconst server = new Server(\n\t\t{ name: \"semantic-search\", version: packageVersion },\n\t\t{ capabilities: { tools: {}, logging: {} } },\n\t);\n\n\tserver.setRequestHandler(ListToolsRequestSchema, async () => ({\n\t\ttools: [SEARCH_TOOL],\n\t}));\n\n\tserver.setRequestHandler(CallToolRequestSchema, async (request): Promise<CallToolResult> => {\n\t\tif (request.params.name !== \"search\") {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `Unknown tool: ${request.params.name}` }],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\n\t\tconst args = (request.params.arguments ?? {}) as {\n\t\t\tquery?: string;\n\t\t\tprojectDir?: string;\n\t\t\tpath?: string;\n\t\t\tlimit?: number;\n\t\t\trebuild?: boolean;\n\t\t};\n\t\tconst { query, path: searchPath, rebuild = false } = args;\n\t\tconst limit = typeof args.limit === \"number\" && args.limit > 0 ? Math.floor(args.limit) : 20;\n\n\t\tconst projectDir = args.projectDir ? resolve(args.projectDir) : defaultProjectDir;\n\n\t\tif (!SearchEngine.isAvailable()) {\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: \"Semantic search requires Node.js 22+ (for built-in SQLite). Current version does not support node:sqlite.\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\n\t\tif (!query || query.trim().length === 0) {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: \"Search query cannot be empty.\" }],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\tconst engine = getSearchEngine(projectDir);\n\n\t\t\tif (rebuild) {\n\t\t\t\tawait engine.resetIndex();\n\t\t\t}\n\n\t\t\t// Send progress via logging messages\n\t\t\tconst results = await engine.search(query, {\n\t\t\t\tlimit,\n\t\t\t\tpathFilter: searchPath,\n\t\t\t\tonProgress: (phase, current, total) => {\n\t\t\t\t\tserver\n\t\t\t\t\t\t.sendLoggingMessage({\n\t\t\t\t\t\t\tlevel: \"info\",\n\t\t\t\t\t\t\tlogger: \"semantic-search\",\n\t\t\t\t\t\t\tdata: `${phase}: ${current}/${total}`,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch(() => {\n\t\t\t\t\t\t\t// Ignore errors sending progress — client may not support logging\n\t\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst text = formatResults(results);\n\t\t\tconst stats = engine.getStats();\n\n\t\t\tlet statsLine = \"\";\n\t\t\tif (stats) {\n\t\t\t\tstatsLine = `\\n\\n[Index: ${stats.files} files, ${stats.chunks} chunks]`;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: text + statsLine }],\n\t\t\t};\n\t\t} catch (err) {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `Search failed: ${err instanceof Error ? err.message : String(err)}` }],\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t}\n\t});\n\n\treturn server;\n}\n\n// ============================================================================\n// Server Startup\n// ============================================================================\n\n/**\n * Create and start an MCP server over stdio.\n * This blocks until the transport is closed.\n */\nexport async function startServer(projectDir: string): Promise<void> {\n\tconst server = createMcpServer(projectDir);\n\tconst transport = new StdioServerTransport();\n\tawait server.connect(transport);\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BM25 metric — full-text search scoring via FTS5.
|
|
3
|
+
*/
|
|
4
|
+
import type { SearchDatabase } from "../db.js";
|
|
5
|
+
/**
|
|
6
|
+
* Compute BM25 scores for a query using FTS5.
|
|
7
|
+
* Returns a Map of chunkId → normalized score (0-1, higher = more relevant).
|
|
8
|
+
*/
|
|
9
|
+
export declare function computeBm25Scores(db: SearchDatabase, query: string, limit: number): Map<number, number>;
|
|
10
|
+
//# sourceMappingURL=bm25.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bm25.d.ts","sourceRoot":"","sources":["../../src/metrics/bm25.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAwBvG","sourcesContent":["/**\n * BM25 metric — full-text search scoring via FTS5.\n */\n\nimport type { SearchDatabase } from \"../db.js\";\n\n/**\n * Compute BM25 scores for a query using FTS5.\n * Returns a Map of chunkId → normalized score (0-1, higher = more relevant).\n */\nexport function computeBm25Scores(db: SearchDatabase, query: string, limit: number): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tconst results = db.ftsSearch(query, limit);\n\t\tif (results.length === 0) return scores;\n\n\t\t// Find the maximum score for normalization\n\t\tlet maxScore = 0;\n\t\tfor (const r of results) {\n\t\t\tif (r.score > maxScore) maxScore = r.score;\n\t\t}\n\n\t\t// Normalize: top result → 1.0, others proportional\n\t\tif (maxScore > 0) {\n\t\t\tfor (const r of results) {\n\t\t\t\tscores.set(r.chunkId, r.score / maxScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If FTS query fails, return empty map\n\t}\n\n\treturn scores;\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BM25 metric — full-text search scoring via FTS5.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Compute BM25 scores for a query using FTS5.
|
|
6
|
+
* Returns a Map of chunkId → normalized score (0-1, higher = more relevant).
|
|
7
|
+
*/
|
|
8
|
+
export function computeBm25Scores(db, query, limit) {
|
|
9
|
+
const scores = new Map();
|
|
10
|
+
try {
|
|
11
|
+
const results = db.ftsSearch(query, limit);
|
|
12
|
+
if (results.length === 0)
|
|
13
|
+
return scores;
|
|
14
|
+
// Find the maximum score for normalization
|
|
15
|
+
let maxScore = 0;
|
|
16
|
+
for (const r of results) {
|
|
17
|
+
if (r.score > maxScore)
|
|
18
|
+
maxScore = r.score;
|
|
19
|
+
}
|
|
20
|
+
// Normalize: top result → 1.0, others proportional
|
|
21
|
+
if (maxScore > 0) {
|
|
22
|
+
for (const r of results) {
|
|
23
|
+
scores.set(r.chunkId, r.score / maxScore);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// If FTS query fails, return empty map
|
|
29
|
+
}
|
|
30
|
+
return scores;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=bm25.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bm25.js","sourceRoot":"","sources":["../../src/metrics/bm25.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAkB,EAAE,KAAa,EAAE,KAAa,EAAuB;IACxG,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC3C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAExC,2CAA2C;QAC3C,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACzB,IAAI,CAAC,CAAC,KAAK,GAAG,QAAQ;gBAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC;QAC5C,CAAC;QAED,qDAAmD;QACnD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YAClB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACzB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC;YAC3C,CAAC;QACF,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,uCAAuC;IACxC,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["/**\n * BM25 metric — full-text search scoring via FTS5.\n */\n\nimport type { SearchDatabase } from \"../db.js\";\n\n/**\n * Compute BM25 scores for a query using FTS5.\n * Returns a Map of chunkId → normalized score (0-1, higher = more relevant).\n */\nexport function computeBm25Scores(db: SearchDatabase, query: string, limit: number): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tconst results = db.ftsSearch(query, limit);\n\t\tif (results.length === 0) return scores;\n\n\t\t// Find the maximum score for normalization\n\t\tlet maxScore = 0;\n\t\tfor (const r of results) {\n\t\t\tif (r.score > maxScore) maxScore = r.score;\n\t\t}\n\n\t\t// Normalize: top result → 1.0, others proportional\n\t\tif (maxScore > 0) {\n\t\t\tfor (const r of results) {\n\t\t\t\tscores.set(r.chunkId, r.score / maxScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If FTS query fails, return empty map\n\t}\n\n\treturn scores;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git recency metric — recently modified files score higher.
|
|
3
|
+
*
|
|
4
|
+
* Runs a single `git log` command to get last-modified timestamps for all
|
|
5
|
+
* files, then applies linear decay scoring.
|
|
6
|
+
*/
|
|
7
|
+
import type { StoredChunk } from "../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Compute git recency scores based on when files were last modified.
|
|
10
|
+
* More recently modified files score higher.
|
|
11
|
+
* Falls back gracefully if git is unavailable.
|
|
12
|
+
*/
|
|
13
|
+
export declare function computeGitRecencyScores(projectRoot: string, chunks: StoredChunk[]): Promise<Map<number, number>>;
|
|
14
|
+
//# sourceMappingURL=git-recency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-recency.d.ts","sourceRoot":"","sources":["../../src/metrics/git-recency.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAa/C;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC5C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,WAAW,EAAE,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAyD9B","sourcesContent":["/**\n * Git recency metric — recently modified files score higher.\n *\n * Runs a single `git log` command to get last-modified timestamps for all\n * files, then applies linear decay scoring.\n */\n\nimport { execFile as execFileCb } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport type { StoredChunk } from \"../types.js\";\n\nconst execFile = promisify(execFileCb);\n\n/** Timeout for the git command in milliseconds. */\nconst GIT_TIMEOUT_MS = 15000;\n\n/** Max buffer for git output (10 MB — sufficient for large repos). */\nconst GIT_MAX_BUFFER = 10 * 1024 * 1024;\n\n/** Default score for files where git info is unavailable. */\nconst NEUTRAL_SCORE = 0.5;\n\n/**\n * Compute git recency scores based on when files were last modified.\n * More recently modified files score higher.\n * Falls back gracefully if git is unavailable.\n */\nexport async function computeGitRecencyScores(\n\tprojectRoot: string,\n\tchunks: StoredChunk[],\n): Promise<Map<number, number>> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tif (chunks.length === 0) return scores;\n\n\t\t// Collect unique file paths\n\t\tconst uniquePaths = new Set<string>();\n\t\tfor (const chunk of chunks) {\n\t\t\tuniquePaths.add(chunk.filePath);\n\t\t}\n\n\t\t// Get last-modified timestamps in a single git call.\n\t\t// Output format: \"COMMIT <timestamp>\" lines followed by changed file names.\n\t\t// We take the first (most recent) timestamp seen for each file.\n\t\tconst fileTimestamps = await getFileTimestamps(projectRoot, uniquePaths);\n\n\t\t// If no timestamps found, assign neutral scores\n\t\tif (fileTimestamps.size === 0) {\n\t\t\tfor (const chunk of chunks) {\n\t\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t\t}\n\t\t\treturn scores;\n\t\t}\n\n\t\t// Find oldest and newest timestamps\n\t\tlet oldest = Infinity;\n\t\tlet newest = -Infinity;\n\t\tfor (const ts of fileTimestamps.values()) {\n\t\t\tif (ts < oldest) oldest = ts;\n\t\t\tif (ts > newest) newest = ts;\n\t\t}\n\n\t\tconst range = newest - oldest;\n\n\t\t// Assign scores to chunks\n\t\tfor (const chunk of chunks) {\n\t\t\tconst ts = fileTimestamps.get(chunk.filePath);\n\t\t\tif (ts === undefined) {\n\t\t\t\t// Not tracked by git → neutral score\n\t\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t\t} else if (range === 0) {\n\t\t\t\t// All files have the same timestamp\n\t\t\t\tscores.set(chunk.id, 1);\n\t\t\t} else {\n\t\t\t\t// Linear decay: newest → 1.0, oldest → 0.0\n\t\t\t\tscores.set(chunk.id, (ts - oldest) / range);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Git unavailable entirely — assign neutral scores\n\t\tfor (const chunk of chunks) {\n\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t}\n\t}\n\n\treturn scores;\n}\n\n/**\n * Get last-modified timestamps for files using a single `git log` invocation.\n * Returns a Map of filePath → unix timestamp (seconds).\n */\nasync function getFileTimestamps(projectRoot: string, targetPaths: Set<string>): Promise<Map<string, number>> {\n\tconst fileTimestamps = new Map<string, number>();\n\n\ttry {\n\t\t// Single git call: list all commits with their timestamps and changed files.\n\t\t// --diff-filter=AMCR: only additions, modifications, copies, renames.\n\t\t// --name-only: list file names after each commit.\n\t\t// --format=\"COMMIT %at\": prefix each commit with its unix timestamp.\n\t\tconst { stdout: output } = await execFile(\n\t\t\t\"git\",\n\t\t\t[\"log\", \"--max-count=10000\", \"--format=COMMIT %at\", \"--name-only\", \"--diff-filter=AMCR\"],\n\t\t\t{\n\t\t\t\tcwd: projectRoot,\n\t\t\t\ttimeout: GIT_TIMEOUT_MS,\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\tmaxBuffer: GIT_MAX_BUFFER,\n\t\t\t},\n\t\t);\n\n\t\tlet currentTimestamp = 0;\n\t\tlet foundAll = false;\n\n\t\tfor (const line of output.split(\"\\n\")) {\n\t\t\tif (foundAll) break;\n\n\t\t\tif (line.startsWith(\"COMMIT \")) {\n\t\t\t\tcurrentTimestamp = Number.parseInt(line.slice(7), 10);\n\t\t\t\tif (Number.isNaN(currentTimestamp) || currentTimestamp <= 0) {\n\t\t\t\t\tcurrentTimestamp = 0;\n\t\t\t\t}\n\t\t\t} else if (line.trim() && currentTimestamp > 0) {\n\t\t\t\tconst filePath = line.trim();\n\t\t\t\t// Only record the first (most recent) timestamp per file\n\t\t\t\tif (targetPaths.has(filePath) && !fileTimestamps.has(filePath)) {\n\t\t\t\t\tfileTimestamps.set(filePath, currentTimestamp);\n\t\t\t\t\t// Early exit once we've found all target files\n\t\t\t\t\tif (fileTimestamps.size === targetPaths.size) {\n\t\t\t\t\t\tfoundAll = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Git unavailable or failed — return empty map\n\t}\n\n\treturn fileTimestamps;\n}\n"]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git recency metric — recently modified files score higher.
|
|
3
|
+
*
|
|
4
|
+
* Runs a single `git log` command to get last-modified timestamps for all
|
|
5
|
+
* files, then applies linear decay scoring.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
const execFile = promisify(execFileCb);
|
|
10
|
+
/** Timeout for the git command in milliseconds. */
|
|
11
|
+
const GIT_TIMEOUT_MS = 15000;
|
|
12
|
+
/** Max buffer for git output (10 MB — sufficient for large repos). */
|
|
13
|
+
const GIT_MAX_BUFFER = 10 * 1024 * 1024;
|
|
14
|
+
/** Default score for files where git info is unavailable. */
|
|
15
|
+
const NEUTRAL_SCORE = 0.5;
|
|
16
|
+
/**
|
|
17
|
+
* Compute git recency scores based on when files were last modified.
|
|
18
|
+
* More recently modified files score higher.
|
|
19
|
+
* Falls back gracefully if git is unavailable.
|
|
20
|
+
*/
|
|
21
|
+
export async function computeGitRecencyScores(projectRoot, chunks) {
|
|
22
|
+
const scores = new Map();
|
|
23
|
+
try {
|
|
24
|
+
if (chunks.length === 0)
|
|
25
|
+
return scores;
|
|
26
|
+
// Collect unique file paths
|
|
27
|
+
const uniquePaths = new Set();
|
|
28
|
+
for (const chunk of chunks) {
|
|
29
|
+
uniquePaths.add(chunk.filePath);
|
|
30
|
+
}
|
|
31
|
+
// Get last-modified timestamps in a single git call.
|
|
32
|
+
// Output format: "COMMIT <timestamp>" lines followed by changed file names.
|
|
33
|
+
// We take the first (most recent) timestamp seen for each file.
|
|
34
|
+
const fileTimestamps = await getFileTimestamps(projectRoot, uniquePaths);
|
|
35
|
+
// If no timestamps found, assign neutral scores
|
|
36
|
+
if (fileTimestamps.size === 0) {
|
|
37
|
+
for (const chunk of chunks) {
|
|
38
|
+
scores.set(chunk.id, NEUTRAL_SCORE);
|
|
39
|
+
}
|
|
40
|
+
return scores;
|
|
41
|
+
}
|
|
42
|
+
// Find oldest and newest timestamps
|
|
43
|
+
let oldest = Infinity;
|
|
44
|
+
let newest = -Infinity;
|
|
45
|
+
for (const ts of fileTimestamps.values()) {
|
|
46
|
+
if (ts < oldest)
|
|
47
|
+
oldest = ts;
|
|
48
|
+
if (ts > newest)
|
|
49
|
+
newest = ts;
|
|
50
|
+
}
|
|
51
|
+
const range = newest - oldest;
|
|
52
|
+
// Assign scores to chunks
|
|
53
|
+
for (const chunk of chunks) {
|
|
54
|
+
const ts = fileTimestamps.get(chunk.filePath);
|
|
55
|
+
if (ts === undefined) {
|
|
56
|
+
// Not tracked by git → neutral score
|
|
57
|
+
scores.set(chunk.id, NEUTRAL_SCORE);
|
|
58
|
+
}
|
|
59
|
+
else if (range === 0) {
|
|
60
|
+
// All files have the same timestamp
|
|
61
|
+
scores.set(chunk.id, 1);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Linear decay: newest → 1.0, oldest → 0.0
|
|
65
|
+
scores.set(chunk.id, (ts - oldest) / range);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Git unavailable entirely — assign neutral scores
|
|
71
|
+
for (const chunk of chunks) {
|
|
72
|
+
scores.set(chunk.id, NEUTRAL_SCORE);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return scores;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get last-modified timestamps for files using a single `git log` invocation.
|
|
79
|
+
* Returns a Map of filePath → unix timestamp (seconds).
|
|
80
|
+
*/
|
|
81
|
+
async function getFileTimestamps(projectRoot, targetPaths) {
|
|
82
|
+
const fileTimestamps = new Map();
|
|
83
|
+
try {
|
|
84
|
+
// Single git call: list all commits with their timestamps and changed files.
|
|
85
|
+
// --diff-filter=AMCR: only additions, modifications, copies, renames.
|
|
86
|
+
// --name-only: list file names after each commit.
|
|
87
|
+
// --format="COMMIT %at": prefix each commit with its unix timestamp.
|
|
88
|
+
const { stdout: output } = await execFile("git", ["log", "--max-count=10000", "--format=COMMIT %at", "--name-only", "--diff-filter=AMCR"], {
|
|
89
|
+
cwd: projectRoot,
|
|
90
|
+
timeout: GIT_TIMEOUT_MS,
|
|
91
|
+
encoding: "utf-8",
|
|
92
|
+
maxBuffer: GIT_MAX_BUFFER,
|
|
93
|
+
});
|
|
94
|
+
let currentTimestamp = 0;
|
|
95
|
+
let foundAll = false;
|
|
96
|
+
for (const line of output.split("\n")) {
|
|
97
|
+
if (foundAll)
|
|
98
|
+
break;
|
|
99
|
+
if (line.startsWith("COMMIT ")) {
|
|
100
|
+
currentTimestamp = Number.parseInt(line.slice(7), 10);
|
|
101
|
+
if (Number.isNaN(currentTimestamp) || currentTimestamp <= 0) {
|
|
102
|
+
currentTimestamp = 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (line.trim() && currentTimestamp > 0) {
|
|
106
|
+
const filePath = line.trim();
|
|
107
|
+
// Only record the first (most recent) timestamp per file
|
|
108
|
+
if (targetPaths.has(filePath) && !fileTimestamps.has(filePath)) {
|
|
109
|
+
fileTimestamps.set(filePath, currentTimestamp);
|
|
110
|
+
// Early exit once we've found all target files
|
|
111
|
+
if (fileTimestamps.size === targetPaths.size) {
|
|
112
|
+
foundAll = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Git unavailable or failed — return empty map
|
|
120
|
+
}
|
|
121
|
+
return fileTimestamps;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=git-recency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-recency.js","sourceRoot":"","sources":["../../src/metrics/git-recency.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Git recency metric — recently modified files score higher.\n *\n * Runs a single `git log` command to get last-modified timestamps for all\n * files, then applies linear decay scoring.\n */\n\nimport { execFile as execFileCb } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport type { StoredChunk } from \"../types.js\";\n\nconst execFile = promisify(execFileCb);\n\n/** Timeout for the git command in milliseconds. */\nconst GIT_TIMEOUT_MS = 15000;\n\n/** Max buffer for git output (10 MB — sufficient for large repos). */\nconst GIT_MAX_BUFFER = 10 * 1024 * 1024;\n\n/** Default score for files where git info is unavailable. */\nconst NEUTRAL_SCORE = 0.5;\n\n/**\n * Compute git recency scores based on when files were last modified.\n * More recently modified files score higher.\n * Falls back gracefully if git is unavailable.\n */\nexport async function computeGitRecencyScores(\n\tprojectRoot: string,\n\tchunks: StoredChunk[],\n): Promise<Map<number, number>> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tif (chunks.length === 0) return scores;\n\n\t\t// Collect unique file paths\n\t\tconst uniquePaths = new Set<string>();\n\t\tfor (const chunk of chunks) {\n\t\t\tuniquePaths.add(chunk.filePath);\n\t\t}\n\n\t\t// Get last-modified timestamps in a single git call.\n\t\t// Output format: \"COMMIT <timestamp>\" lines followed by changed file names.\n\t\t// We take the first (most recent) timestamp seen for each file.\n\t\tconst fileTimestamps = await getFileTimestamps(projectRoot, uniquePaths);\n\n\t\t// If no timestamps found, assign neutral scores\n\t\tif (fileTimestamps.size === 0) {\n\t\t\tfor (const chunk of chunks) {\n\t\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t\t}\n\t\t\treturn scores;\n\t\t}\n\n\t\t// Find oldest and newest timestamps\n\t\tlet oldest = Infinity;\n\t\tlet newest = -Infinity;\n\t\tfor (const ts of fileTimestamps.values()) {\n\t\t\tif (ts < oldest) oldest = ts;\n\t\t\tif (ts > newest) newest = ts;\n\t\t}\n\n\t\tconst range = newest - oldest;\n\n\t\t// Assign scores to chunks\n\t\tfor (const chunk of chunks) {\n\t\t\tconst ts = fileTimestamps.get(chunk.filePath);\n\t\t\tif (ts === undefined) {\n\t\t\t\t// Not tracked by git → neutral score\n\t\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t\t} else if (range === 0) {\n\t\t\t\t// All files have the same timestamp\n\t\t\t\tscores.set(chunk.id, 1);\n\t\t\t} else {\n\t\t\t\t// Linear decay: newest → 1.0, oldest → 0.0\n\t\t\t\tscores.set(chunk.id, (ts - oldest) / range);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Git unavailable entirely — assign neutral scores\n\t\tfor (const chunk of chunks) {\n\t\t\tscores.set(chunk.id, NEUTRAL_SCORE);\n\t\t}\n\t}\n\n\treturn scores;\n}\n\n/**\n * Get last-modified timestamps for files using a single `git log` invocation.\n * Returns a Map of filePath → unix timestamp (seconds).\n */\nasync function getFileTimestamps(projectRoot: string, targetPaths: Set<string>): Promise<Map<string, number>> {\n\tconst fileTimestamps = new Map<string, number>();\n\n\ttry {\n\t\t// Single git call: list all commits with their timestamps and changed files.\n\t\t// --diff-filter=AMCR: only additions, modifications, copies, renames.\n\t\t// --name-only: list file names after each commit.\n\t\t// --format=\"COMMIT %at\": prefix each commit with its unix timestamp.\n\t\tconst { stdout: output } = await execFile(\n\t\t\t\"git\",\n\t\t\t[\"log\", \"--max-count=10000\", \"--format=COMMIT %at\", \"--name-only\", \"--diff-filter=AMCR\"],\n\t\t\t{\n\t\t\t\tcwd: projectRoot,\n\t\t\t\ttimeout: GIT_TIMEOUT_MS,\n\t\t\t\tencoding: \"utf-8\",\n\t\t\t\tmaxBuffer: GIT_MAX_BUFFER,\n\t\t\t},\n\t\t);\n\n\t\tlet currentTimestamp = 0;\n\t\tlet foundAll = false;\n\n\t\tfor (const line of output.split(\"\\n\")) {\n\t\t\tif (foundAll) break;\n\n\t\t\tif (line.startsWith(\"COMMIT \")) {\n\t\t\t\tcurrentTimestamp = Number.parseInt(line.slice(7), 10);\n\t\t\t\tif (Number.isNaN(currentTimestamp) || currentTimestamp <= 0) {\n\t\t\t\t\tcurrentTimestamp = 0;\n\t\t\t\t}\n\t\t\t} else if (line.trim() && currentTimestamp > 0) {\n\t\t\t\tconst filePath = line.trim();\n\t\t\t\t// Only record the first (most recent) timestamp per file\n\t\t\t\tif (targetPaths.has(filePath) && !fileTimestamps.has(filePath)) {\n\t\t\t\t\tfileTimestamps.set(filePath, currentTimestamp);\n\t\t\t\t\t// Early exit once we've found all target files\n\t\t\t\t\tif (fileTimestamps.size === targetPaths.size) {\n\t\t\t\t\t\tfoundAll = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// Git unavailable or failed — return empty map\n\t}\n\n\treturn fileTimestamps;\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import graph proximity metric.
|
|
3
|
+
*
|
|
4
|
+
* Files that import or are imported by high-scoring files get a boost.
|
|
5
|
+
* Uses a simple 1-hop propagation from seed scores.
|
|
6
|
+
*/
|
|
7
|
+
import type { SearchDatabase } from "../db.js";
|
|
8
|
+
/**
|
|
9
|
+
* Compute import graph proximity scores.
|
|
10
|
+
* Files that import/are imported by high-scoring files get a boost.
|
|
11
|
+
* Seed files also get a connectivity bonus based on how many of their
|
|
12
|
+
* neighbors are in the seed set.
|
|
13
|
+
*/
|
|
14
|
+
export declare function computeImportGraphScores(db: SearchDatabase, seedScores: Map<number, number>, fileIdToChunkIds: Map<number, number[]>): Map<number, number>;
|
|
15
|
+
//# sourceMappingURL=import-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-graph.d.ts","sourceRoot":"","sources":["../../src/metrics/import-graph.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAQ/C;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACvC,EAAE,EAAE,cAAc,EAClB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACrC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAqGrB","sourcesContent":["/**\n * Import graph proximity metric.\n *\n * Files that import or are imported by high-scoring files get a boost.\n * Uses a simple 1-hop propagation from seed scores.\n */\n\nimport type { SearchDatabase } from \"../db.js\";\n\n/** Fraction of seed score propagated to neighbors. */\nconst PROPAGATION_FACTOR = 0.5;\n\n/** Fraction of propagated score given back to seed files with many connections. */\nconst SELF_BOOST_FACTOR = 0.25;\n\n/**\n * Compute import graph proximity scores.\n * Files that import/are imported by high-scoring files get a boost.\n * Seed files also get a connectivity bonus based on how many of their\n * neighbors are in the seed set.\n */\nexport function computeImportGraphScores(\n\tdb: SearchDatabase,\n\tseedScores: Map<number, number>,\n\tfileIdToChunkIds: Map<number, number[]>,\n): Map<number, number> {\n\tconst scores = new Map<number, number>();\n\n\ttry {\n\t\tif (seedScores.size === 0) return scores;\n\n\t\t// Build fileId → filePath lookup and extension-stripped path index\n\t\tconst allFiles = db.getAllFiles();\n\t\tconst fileIdToPath = new Map<number, string>();\n\t\tconst pathToFileId = new Map<string, number>();\n\t\tconst strippedToFileId = new Map<string, number>();\n\n\t\tfor (const f of allFiles) {\n\t\t\tfileIdToPath.set(f.id, f.filePath);\n\t\t\tpathToFileId.set(f.filePath, f.id);\n\t\t\t// Also index without extension so import paths (which strip .js/.ts)\n\t\t\t// can match stored file paths (which keep the extension)\n\t\t\tconst stripped = stripExtension(f.filePath);\n\t\t\tif (!strippedToFileId.has(stripped)) {\n\t\t\t\tstrippedToFileId.set(stripped, f.id);\n\t\t\t}\n\t\t}\n\n\t\t/** Resolve an import target path to a fileId. Tries exact match first, then extension-stripped. */\n\t\tfunction resolveTarget(targetPath: string): number | undefined {\n\t\t\treturn pathToFileId.get(targetPath) ?? strippedToFileId.get(targetPath);\n\t\t}\n\n\t\t// Propagate scores to neighbors\n\t\tconst propagated = new Map<number, number>(); // fileId → accumulated propagated score\n\n\t\tfor (const [fileId, seedScore] of seedScores) {\n\t\t\tconst propagatedScore = seedScore * PROPAGATION_FACTOR;\n\t\t\tif (propagatedScore <= 0) continue;\n\n\t\t\tlet connectedSeedCount = 0;\n\n\t\t\t// Files this file imports\n\t\t\tconst importedPaths = db.getImportsFrom(fileId);\n\t\t\tfor (const targetPath of importedPaths) {\n\t\t\t\tconst targetFileId = resolveTarget(targetPath);\n\t\t\t\tif (targetFileId === undefined) continue;\n\n\t\t\t\tif (seedScores.has(targetFileId)) {\n\t\t\t\t\tconnectedSeedCount++;\n\t\t\t\t} else {\n\t\t\t\t\tpropagated.set(targetFileId, (propagated.get(targetFileId) ?? 0) + propagatedScore);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Files that import this file\n\t\t\tconst filePath = fileIdToPath.get(fileId);\n\t\t\tif (filePath) {\n\t\t\t\tconst importerIds = db.getImportersOf(filePath);\n\t\t\t\t// Also check extension-stripped variant\n\t\t\t\tconst stripped = stripExtension(filePath);\n\t\t\t\tconst strippedImporterIds = stripped !== filePath ? db.getImportersOf(stripped) : [];\n\t\t\t\tconst allImporterIds = new Set([...importerIds, ...strippedImporterIds]);\n\n\t\t\t\tfor (const importerFileId of allImporterIds) {\n\t\t\t\t\tif (seedScores.has(importerFileId)) {\n\t\t\t\t\t\tconnectedSeedCount++;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tpropagated.set(importerFileId, (propagated.get(importerFileId) ?? 0) + propagatedScore);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Self-boost: seed files with many connections to other seeds get a bonus\n\t\t\tif (connectedSeedCount > 0) {\n\t\t\t\tconst selfBoost = seedScore * SELF_BOOST_FACTOR * Math.min(connectedSeedCount / 3, 1);\n\t\t\t\tpropagated.set(fileId, (propagated.get(fileId) ?? 0) + selfBoost);\n\t\t\t}\n\t\t}\n\n\t\tif (propagated.size === 0) return scores;\n\n\t\t// Find max for normalization\n\t\tlet maxScore = 0;\n\t\tfor (const score of propagated.values()) {\n\t\t\tif (score > maxScore) maxScore = score;\n\t\t}\n\n\t\tif (maxScore <= 0) return scores;\n\n\t\t// Distribute to chunks and normalize\n\t\tfor (const [fileId, fileScore] of propagated) {\n\t\t\tconst chunkIds = fileIdToChunkIds.get(fileId);\n\t\t\tif (!chunkIds || chunkIds.length === 0) continue;\n\n\t\t\t// Distribute equally among chunks in this file\n\t\t\tconst perChunkScore = fileScore / maxScore / chunkIds.length;\n\t\t\tfor (const chunkId of chunkIds) {\n\t\t\t\tscores.set(chunkId, perChunkScore);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// If anything fails, return empty map\n\t}\n\n\treturn scores;\n}\n\n/** Strip common source file extensions for path matching. */\nfunction stripExtension(filePath: string): string {\n\treturn filePath.replace(/\\.[jt]sx?$|\\.py$|\\.go$|\\.rs$|\\.java$|\\.c$|\\.h$|\\.cpp$|\\.hpp$|\\.cc$|\\.cxx$/, \"\");\n}\n"]}
|