@dreb/coding-agent 1.16.0 → 2.0.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/README.md +19 -9
- package/agents/code-reviewer.md +1 -1
- package/agents/completeness-checker.md +1 -1
- package/agents/error-auditor.md +1 -1
- package/agents/explore.md +1 -1
- package/agents/feature-dev.md +1 -1
- package/agents/independent-assessor.md +1 -1
- package/agents/simplifier.md +1 -1
- package/agents/test-reviewer.md +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +7 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/dreb-paths.d.ts +17 -0
- package/dist/core/tools/dreb-paths.d.ts.map +1 -0
- package/dist/core/tools/dreb-paths.js +43 -0
- package/dist/core/tools/dreb-paths.js.map +1 -0
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +8 -0
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +8 -0
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/search.d.ts +19 -1
- package/dist/core/tools/search.d.ts.map +1 -1
- package/dist/core/tools/search.js +50 -44
- package/dist/core/tools/search.js.map +1 -1
- package/package.json +2 -1
- package/dist/core/search/chunker.d.ts +0 -21
- package/dist/core/search/chunker.d.ts.map +0 -1
- package/dist/core/search/chunker.js +0 -51
- package/dist/core/search/chunker.js.map +0 -1
- package/dist/core/search/db.d.ts +0 -89
- package/dist/core/search/db.d.ts.map +0 -1
- package/dist/core/search/db.js +0 -406
- package/dist/core/search/db.js.map +0 -1
- package/dist/core/search/embedder.d.ts +0 -51
- package/dist/core/search/embedder.d.ts.map +0 -1
- package/dist/core/search/embedder.js +0 -143
- package/dist/core/search/embedder.js.map +0 -1
- package/dist/core/search/index-manager.d.ts +0 -55
- package/dist/core/search/index-manager.d.ts.map +0 -1
- package/dist/core/search/index-manager.js +0 -311
- package/dist/core/search/index-manager.js.map +0 -1
- package/dist/core/search/metrics/bm25.d.ts +0 -10
- package/dist/core/search/metrics/bm25.d.ts.map +0 -1
- package/dist/core/search/metrics/bm25.js +0 -32
- package/dist/core/search/metrics/bm25.js.map +0 -1
- package/dist/core/search/metrics/git-recency.d.ts +0 -14
- package/dist/core/search/metrics/git-recency.d.ts.map +0 -1
- package/dist/core/search/metrics/git-recency.js +0 -123
- package/dist/core/search/metrics/git-recency.js.map +0 -1
- package/dist/core/search/metrics/import-graph.d.ts +0 -15
- package/dist/core/search/metrics/import-graph.d.ts.map +0 -1
- package/dist/core/search/metrics/import-graph.js +0 -115
- package/dist/core/search/metrics/import-graph.js.map +0 -1
- package/dist/core/search/metrics/path-match.d.ts +0 -13
- package/dist/core/search/metrics/path-match.d.ts.map +0 -1
- package/dist/core/search/metrics/path-match.js +0 -54
- package/dist/core/search/metrics/path-match.js.map +0 -1
- package/dist/core/search/metrics/symbol-match.d.ts +0 -12
- package/dist/core/search/metrics/symbol-match.d.ts.map +0 -1
- package/dist/core/search/metrics/symbol-match.js +0 -62
- package/dist/core/search/metrics/symbol-match.js.map +0 -1
- package/dist/core/search/metrics/tokenize.d.ts +0 -12
- package/dist/core/search/metrics/tokenize.d.ts.map +0 -1
- package/dist/core/search/metrics/tokenize.js +0 -29
- package/dist/core/search/metrics/tokenize.js.map +0 -1
- package/dist/core/search/poem.d.ts +0 -38
- package/dist/core/search/poem.d.ts.map +0 -1
- package/dist/core/search/poem.js +0 -214
- package/dist/core/search/poem.js.map +0 -1
- package/dist/core/search/query-classifier.d.ts +0 -17
- package/dist/core/search/query-classifier.d.ts.map +0 -1
- package/dist/core/search/query-classifier.js +0 -54
- package/dist/core/search/query-classifier.js.map +0 -1
- package/dist/core/search/scanner.d.ts +0 -30
- package/dist/core/search/scanner.d.ts.map +0 -1
- package/dist/core/search/scanner.js +0 -335
- package/dist/core/search/scanner.js.map +0 -1
- package/dist/core/search/search.d.ts +0 -42
- package/dist/core/search/search.d.ts.map +0 -1
- package/dist/core/search/search.js +0 -337
- package/dist/core/search/search.js.map +0 -1
- package/dist/core/search/text-chunker.d.ts +0 -15
- package/dist/core/search/text-chunker.d.ts.map +0 -1
- package/dist/core/search/text-chunker.js +0 -580
- package/dist/core/search/text-chunker.js.map +0 -1
- package/dist/core/search/tree-sitter-chunker.d.ts +0 -25
- package/dist/core/search/tree-sitter-chunker.d.ts.map +0 -1
- package/dist/core/search/tree-sitter-chunker.js +0 -357
- package/dist/core/search/tree-sitter-chunker.js.map +0 -1
- package/dist/core/search/types.d.ts +0 -96
- package/dist/core/search/types.d.ts.map +0 -1
- package/dist/core/search/types.js +0 -6
- package/dist/core/search/types.js.map +0 -1
- package/dist/core/search/vector-store.d.ts +0 -43
- package/dist/core/search/vector-store.d.ts.map +0 -1
- package/dist/core/search/vector-store.js +0 -73
- package/dist/core/search/vector-store.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/core/search/db.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQLite database abstraction for the search index.\n *\n * Uses `node:sqlite` (built-in Node 22+). Feature-gated — callers must check\n * availability via `isSqliteAvailable()` before constructing a SearchDatabase.\n */\n\nimport { createRequire } from \"node:module\";\nimport type { ChunkKind, FileType, IndexedFile, StoredChunk } from \"./types.js\";\nimport { unpackVector } from \"./vector-store.js\";\n\n// ============================================================================\n// Availability Check\n// ============================================================================\n\nconst require = createRequire(import.meta.url);\nlet _sqliteAvailable: boolean | null = null;\n\n/** Check whether `node:sqlite` is available in this Node.js runtime. */\nexport function isSqliteAvailable(): boolean {\n\tif (_sqliteAvailable !== null) return _sqliteAvailable;\n\ttry {\n\t\trequire(\"node:sqlite\");\n\t\t_sqliteAvailable = true;\n\t} catch {\n\t\t_sqliteAvailable = false;\n\t}\n\treturn _sqliteAvailable;\n}\n\n// ============================================================================\n// Schema Version\n// ============================================================================\n\nconst SCHEMA_VERSION = 1;\n\n// ============================================================================\n// Database\n// ============================================================================\n\n/** Wrapper around `node:sqlite` DatabaseSync for the search index. */\nexport class SearchDatabase {\n\tprivate db: any; // DatabaseSync from node:sqlite\n\n\tconstructor(dbPath: string) {\n\t\t// Import synchronously — caller must have verified availability\n\t\tconst { DatabaseSync } = require(\"node:sqlite\");\n\t\tthis.db = new DatabaseSync(dbPath);\n\t\tthis.db.exec(\"PRAGMA journal_mode=WAL\");\n\t\tthis.db.exec(\"PRAGMA synchronous=NORMAL\");\n\t\tthis.db.exec(\"PRAGMA foreign_keys=ON\");\n\t\tthis.initSchema();\n\t}\n\n\t/** Create or migrate the database schema. */\n\tprivate initSchema(): void {\n\t\t// Check schema version\n\t\tthis.db.exec(\"CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)\");\n\t\tconst versionRow = this.db.prepare(\"SELECT value FROM meta WHERE key = 'schema_version'\").get();\n\t\tconst currentVersion = versionRow ? Number.parseInt(versionRow.value, 10) : 0;\n\n\t\tif (currentVersion >= SCHEMA_VERSION) return;\n\n\t\t// Files table\n\t\tthis.db.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS files (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tfile_path TEXT NOT NULL UNIQUE,\n\t\t\t\tmtime REAL NOT NULL,\n\t\t\t\tfile_type TEXT NOT NULL\n\t\t\t)\n\t\t`);\n\n\t\t// Chunks table\n\t\tthis.db.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS chunks (\n\t\t\t\tid INTEGER PRIMARY KEY AUTOINCREMENT,\n\t\t\t\tfile_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,\n\t\t\t\tfile_path TEXT NOT NULL,\n\t\t\t\tstart_line INTEGER NOT NULL,\n\t\t\t\tend_line INTEGER NOT NULL,\n\t\t\t\tkind TEXT NOT NULL,\n\t\t\t\tname TEXT,\n\t\t\t\tcontent TEXT NOT NULL,\n\t\t\t\tfile_type TEXT NOT NULL\n\t\t\t)\n\t\t`);\n\t\tthis.db.exec(\"CREATE INDEX IF NOT EXISTS idx_chunks_file_id ON chunks(file_id)\");\n\t\tthis.db.exec(\"CREATE INDEX IF NOT EXISTS idx_chunks_file_path ON chunks(file_path)\");\n\n\t\t// FTS5 virtual table (content-synced with chunks)\n\t\tthis.db.exec(`\n\t\t\tCREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(\n\t\t\t\tcontent,\n\t\t\t\tname,\n\t\t\t\tfile_path,\n\t\t\t\tcontent='chunks',\n\t\t\t\tcontent_rowid='id',\n\t\t\t\ttokenize='porter unicode61'\n\t\t\t)\n\t\t`);\n\n\t\t// FTS triggers for incremental updates\n\t\tthis.db.exec(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN\n\t\t\t\tINSERT INTO chunks_fts(rowid, content, name, file_path)\n\t\t\t\tVALUES (new.id, new.content, new.name, new.file_path);\n\t\t\tEND\n\t\t`);\n\t\tthis.db.exec(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN\n\t\t\t\tINSERT INTO chunks_fts(chunks_fts, rowid, content, name, file_path)\n\t\t\t\tVALUES ('delete', old.id, old.content, old.name, old.file_path);\n\t\t\tEND\n\t\t`);\n\t\tthis.db.exec(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN\n\t\t\t\tINSERT INTO chunks_fts(chunks_fts, rowid, content, name, file_path)\n\t\t\t\tVALUES ('delete', old.id, old.content, old.name, old.file_path);\n\t\t\t\tINSERT INTO chunks_fts(rowid, content, name, file_path)\n\t\t\t\tVALUES (new.id, new.content, new.name, new.file_path);\n\t\t\tEND\n\t\t`);\n\n\t\t// Embeddings table (keyed by model name for multi-model support)\n\t\tthis.db.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS embeddings (\n\t\t\t\tchunk_id INTEGER NOT NULL REFERENCES chunks(id) ON DELETE CASCADE,\n\t\t\t\tmodel_name TEXT NOT NULL,\n\t\t\t\tvector BLOB NOT NULL,\n\t\t\t\tPRIMARY KEY (chunk_id, model_name)\n\t\t\t)\n\t\t`);\n\n\t\t// Imports table (import graph edges between files)\n\t\tthis.db.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS imports (\n\t\t\t\tsource_file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,\n\t\t\t\ttarget_file_path TEXT NOT NULL,\n\t\t\t\tPRIMARY KEY (source_file_id, target_file_path)\n\t\t\t)\n\t\t`);\n\t\tthis.db.exec(\"CREATE INDEX IF NOT EXISTS idx_imports_target ON imports(target_file_path)\");\n\n\t\t// Symbols table (symbol names extracted from chunks)\n\t\tthis.db.exec(`\n\t\t\tCREATE TABLE IF NOT EXISTS symbols (\n\t\t\t\tchunk_id INTEGER NOT NULL REFERENCES chunks(id) ON DELETE CASCADE,\n\t\t\t\tname TEXT NOT NULL,\n\t\t\t\tkind TEXT NOT NULL\n\t\t\t)\n\t\t`);\n\t\tthis.db.exec(\"CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name)\");\n\n\t\t// Set schema version\n\t\tthis.db\n\t\t\t.prepare(\"INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)\")\n\t\t\t.run(String(SCHEMA_VERSION));\n\t}\n\n\t// ========================================================================\n\t// Files\n\t// ========================================================================\n\n\t/** Insert or update a file record. Returns the file ID. */\n\tupsertFile(filePath: string, mtime: number, fileType: FileType): number {\n\t\tconst existing = this.db.prepare(\"SELECT id FROM files WHERE file_path = ?\").get(filePath);\n\t\tif (existing) {\n\t\t\tthis.db.prepare(\"UPDATE files SET mtime = ?, file_type = ? WHERE id = ?\").run(mtime, fileType, existing.id);\n\t\t\treturn existing.id;\n\t\t}\n\t\tconst result = this.db\n\t\t\t.prepare(\"INSERT INTO files (file_path, mtime, file_type) VALUES (?, ?, ?)\")\n\t\t\t.run(filePath, mtime, fileType);\n\t\treturn Number(result.lastInsertRowid);\n\t}\n\n\t/** Get a file by path. */\n\tgetFile(filePath: string): IndexedFile | null {\n\t\tconst row = this.db\n\t\t\t.prepare(\"SELECT id, file_path, mtime, file_type FROM files WHERE file_path = ?\")\n\t\t\t.get(filePath);\n\t\tif (!row) return null;\n\t\treturn { id: row.id, filePath: row.file_path, mtime: row.mtime, fileType: row.file_type as FileType };\n\t}\n\n\t/** Get all indexed files. */\n\tgetAllFiles(): IndexedFile[] {\n\t\tconst rows = this.db.prepare(\"SELECT id, file_path, mtime, file_type FROM files\").all();\n\t\treturn rows.map((r: any) => ({\n\t\t\tid: r.id,\n\t\t\tfilePath: r.file_path,\n\t\t\tmtime: r.mtime,\n\t\t\tfileType: r.file_type as FileType,\n\t\t}));\n\t}\n\n\t/** Delete a file and all its chunks/embeddings/symbols (cascading). */\n\tdeleteFile(fileId: number): void {\n\t\tthis.db.prepare(\"DELETE FROM files WHERE id = ?\").run(fileId);\n\t}\n\n\t// ========================================================================\n\t// Chunks\n\t// ========================================================================\n\n\t/** Insert a chunk. Returns the chunk ID. */\n\tinsertChunk(\n\t\tfileId: number,\n\t\tfilePath: string,\n\t\tstartLine: number,\n\t\tendLine: number,\n\t\tkind: ChunkKind,\n\t\tname: string | null,\n\t\tcontent: string,\n\t\tfileType: FileType,\n\t): number {\n\t\tconst result = this.db\n\t\t\t.prepare(\n\t\t\t\t\"INSERT INTO chunks (file_id, file_path, start_line, end_line, kind, name, content, file_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)\",\n\t\t\t)\n\t\t\t.run(fileId, filePath, startLine, endLine, kind, name, content, fileType);\n\t\treturn Number(result.lastInsertRowid);\n\t}\n\n\t/** Delete all chunks for a file. */\n\tdeleteChunksForFile(fileId: number): void {\n\t\tthis.db.prepare(\"DELETE FROM chunks WHERE file_id = ?\").run(fileId);\n\t}\n\n\t/** Get all chunks. */\n\tgetAllChunks(): StoredChunk[] {\n\t\tconst rows = this.db\n\t\t\t.prepare(\"SELECT id, file_id, file_path, start_line, end_line, kind, name, content, file_type FROM chunks\")\n\t\t\t.all();\n\t\treturn rows.map((r: any) => this.rowToChunk(r));\n\t}\n\n\t/** Get chunks by file ID. */\n\tgetChunksByFileId(fileId: number): StoredChunk[] {\n\t\tconst rows = this.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT id, file_id, file_path, start_line, end_line, kind, name, content, file_type FROM chunks WHERE file_id = ?\",\n\t\t\t)\n\t\t\t.all(fileId);\n\t\treturn rows.map((r: any) => this.rowToChunk(r));\n\t}\n\n\t/** Get a chunk by ID. */\n\tgetChunk(chunkId: number): StoredChunk | null {\n\t\tconst row = this.db\n\t\t\t.prepare(\n\t\t\t\t\"SELECT id, file_id, file_path, start_line, end_line, kind, name, content, file_type FROM chunks WHERE id = ?\",\n\t\t\t)\n\t\t\t.get(chunkId);\n\t\tif (!row) return null;\n\t\treturn this.rowToChunk(row);\n\t}\n\n\t/** Get multiple chunks by IDs. Batches queries to avoid exceeding SQLite's bind variable limit. */\n\tgetChunksById(chunkIds: number[]): StoredChunk[] {\n\t\tif (chunkIds.length === 0) return [];\n\n\t\tconst BATCH_SIZE = 500;\n\t\tconst results: StoredChunk[] = [];\n\n\t\tfor (let i = 0; i < chunkIds.length; i += BATCH_SIZE) {\n\t\t\tconst batch = chunkIds.slice(i, i + BATCH_SIZE);\n\t\t\tconst placeholders = batch.map(() => \"?\").join(\",\");\n\t\t\tconst rows = this.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT id, file_id, file_path, start_line, end_line, kind, name, content, file_type FROM chunks WHERE id IN (${placeholders})`,\n\t\t\t\t)\n\t\t\t\t.all(...batch);\n\t\t\tfor (const r of rows) {\n\t\t\t\tresults.push(this.rowToChunk(r));\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tprivate rowToChunk(r: any): StoredChunk {\n\t\treturn {\n\t\t\tid: r.id,\n\t\t\tfileId: r.file_id,\n\t\t\tfilePath: r.file_path,\n\t\t\tstartLine: r.start_line,\n\t\t\tendLine: r.end_line,\n\t\t\tkind: r.kind as ChunkKind,\n\t\t\tname: r.name,\n\t\t\tcontent: r.content,\n\t\t\tfileType: r.file_type as FileType,\n\t\t};\n\t}\n\n\t// ========================================================================\n\t// Embeddings\n\t// ========================================================================\n\n\t/** Store an embedding vector for a chunk. */\n\tupsertEmbedding(chunkId: number, modelName: string, vector: Float32Array): void {\n\t\tconst blob = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);\n\t\tthis.db\n\t\t\t.prepare(\"INSERT OR REPLACE INTO embeddings (chunk_id, model_name, vector) VALUES (?, ?, ?)\")\n\t\t\t.run(chunkId, modelName, blob);\n\t}\n\n\t/** Batch insert embeddings. Uses a transaction for performance. */\n\tbatchUpsertEmbeddings(items: Array<{ chunkId: number; modelName: string; vector: Float32Array }>): void {\n\t\tconst stmt = this.db.prepare(\"INSERT OR REPLACE INTO embeddings (chunk_id, model_name, vector) VALUES (?, ?, ?)\");\n\t\tthis.transaction(() => {\n\t\t\tfor (const item of items) {\n\t\t\t\tconst blob = Buffer.from(item.vector.buffer, item.vector.byteOffset, item.vector.byteLength);\n\t\t\t\tstmt.run(item.chunkId, item.modelName, blob);\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Get the embedding for a chunk. */\n\tgetEmbedding(chunkId: number, modelName: string): Float32Array | null {\n\t\tconst row = this.db\n\t\t\t.prepare(\"SELECT vector FROM embeddings WHERE chunk_id = ? AND model_name = ?\")\n\t\t\t.get(chunkId, modelName);\n\t\tif (!row) return null;\n\t\treturn unpackVector(row.vector);\n\t}\n\n\t/** Get all embeddings for a model. Returns map of chunkId → vector. */\n\tgetAllEmbeddings(modelName: string): Map<number, Float32Array> {\n\t\tconst rows = this.db.prepare(\"SELECT chunk_id, vector FROM embeddings WHERE model_name = ?\").all(modelName);\n\t\tconst map = new Map<number, Float32Array>();\n\t\tfor (const row of rows) {\n\t\t\tmap.set(row.chunk_id, unpackVector(row.vector));\n\t\t}\n\t\treturn map;\n\t}\n\n\t/** Get chunk IDs that have no embedding for a given model. */\n\tgetChunkIdsWithoutEmbedding(modelName: string): number[] {\n\t\tconst rows = this.db\n\t\t\t.prepare(\n\t\t\t\t`SELECT c.id FROM chunks c\n\t\t\t\t LEFT JOIN embeddings e ON c.id = e.chunk_id AND e.model_name = ?\n\t\t\t\t WHERE e.chunk_id IS NULL`,\n\t\t\t)\n\t\t\t.all(modelName);\n\t\treturn rows.map((r: any) => r.id);\n\t}\n\n\t// ========================================================================\n\t// FTS5\n\t// ========================================================================\n\n\t/** Rebuild the FTS5 index (use after bulk operations). */\n\trebuildFts(): void {\n\t\tthis.db.exec(\"INSERT INTO chunks_fts(chunks_fts) VALUES ('rebuild')\");\n\t}\n\n\t/**\n\t * Search via FTS5 with BM25 ranking.\n\t * Returns chunk IDs with their BM25 scores (negated so higher = better).\n\t */\n\tftsSearch(query: string, limit: number): Array<{ chunkId: number; score: number }> {\n\t\ttry {\n\t\t\tconst rows = this.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT chunks_fts.rowid as chunk_id, -bm25(chunks_fts, 1.0, 10.0, 5.0) as score\n\t\t\t\t\t FROM chunks_fts\n\t\t\t\t\t WHERE chunks_fts MATCH ?\n\t\t\t\t\t ORDER BY score DESC\n\t\t\t\t\t LIMIT ?`,\n\t\t\t\t)\n\t\t\t\t.all(query, limit);\n\t\t\treturn rows.map((r: any) => ({ chunkId: r.chunk_id, score: r.score }));\n\t\t} catch {\n\t\t\t// FTS5 MATCH can fail on malformed queries\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t// ========================================================================\n\t// Imports\n\t// ========================================================================\n\n\t/** Record an import edge. */\n\tinsertImport(sourceFileId: number, targetFilePath: string): void {\n\t\tthis.db\n\t\t\t.prepare(\"INSERT OR IGNORE INTO imports (source_file_id, target_file_path) VALUES (?, ?)\")\n\t\t\t.run(sourceFileId, targetFilePath);\n\t}\n\n\t/** Delete all imports for a source file. */\n\tdeleteImportsForFile(sourceFileId: number): void {\n\t\tthis.db.prepare(\"DELETE FROM imports WHERE source_file_id = ?\").run(sourceFileId);\n\t}\n\n\t/** Get files imported by a given source file. */\n\tgetImportsFrom(sourceFileId: number): string[] {\n\t\tconst rows = this.db.prepare(\"SELECT target_file_path FROM imports WHERE source_file_id = ?\").all(sourceFileId);\n\t\treturn rows.map((r: any) => r.target_file_path);\n\t}\n\n\t/** Get file IDs that import a given target path. */\n\tgetImportersOf(targetFilePath: string): number[] {\n\t\tconst rows = this.db.prepare(\"SELECT source_file_id FROM imports WHERE target_file_path = ?\").all(targetFilePath);\n\t\treturn rows.map((r: any) => r.source_file_id);\n\t}\n\n\t/** Get all import edges. */\n\tgetAllImports(): Array<{ sourceFileId: number; targetFilePath: string }> {\n\t\tconst rows = this.db.prepare(\"SELECT source_file_id, target_file_path FROM imports\").all();\n\t\treturn rows.map((r: any) => ({ sourceFileId: r.source_file_id, targetFilePath: r.target_file_path }));\n\t}\n\n\t// ========================================================================\n\t// Symbols\n\t// ========================================================================\n\n\t/** Insert a symbol. */\n\tinsertSymbol(chunkId: number, name: string, kind: string): void {\n\t\tthis.db.prepare(\"INSERT INTO symbols (chunk_id, name, kind) VALUES (?, ?, ?)\").run(chunkId, name, kind);\n\t}\n\n\t/** Delete symbols for a chunk. */\n\tdeleteSymbolsForChunk(chunkId: number): void {\n\t\tthis.db.prepare(\"DELETE FROM symbols WHERE chunk_id = ?\").run(chunkId);\n\t}\n\n\t/** Get all symbols. Returns map of chunkId → symbol names. */\n\tgetAllSymbols(): Map<number, string[]> {\n\t\tconst rows = this.db.prepare(\"SELECT chunk_id, name FROM symbols\").all();\n\t\tconst map = new Map<number, string[]>();\n\t\tfor (const row of rows) {\n\t\t\tconst existing = map.get(row.chunk_id);\n\t\t\tif (existing) existing.push(row.name);\n\t\t\telse map.set(row.chunk_id, [row.name]);\n\t\t}\n\t\treturn map;\n\t}\n\n\t// ========================================================================\n\t// Transaction Helpers\n\t// ========================================================================\n\n\t/** Run a function inside a transaction. */\n\ttransaction<T>(fn: () => T): T {\n\t\tthis.db.exec(\"BEGIN\");\n\t\ttry {\n\t\t\tconst result = fn();\n\t\t\tthis.db.exec(\"COMMIT\");\n\t\t\treturn result;\n\t\t} catch (err) {\n\t\t\tthis.db.exec(\"ROLLBACK\");\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Get the total number of chunks. */\n\tgetChunkCount(): number {\n\t\tconst row = this.db.prepare(\"SELECT COUNT(*) as count FROM chunks\").get();\n\t\treturn row.count;\n\t}\n\n\t/** Get the total number of files. */\n\tgetFileCount(): number {\n\t\tconst row = this.db.prepare(\"SELECT COUNT(*) as count FROM files\").get();\n\t\treturn row.count;\n\t}\n\n\t/** Close the database connection. */\n\tclose(): void {\n\t\tthis.db.close();\n\t}\n}\n"]}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Embedding pipeline using @huggingface/transformers.
|
|
3
|
-
*
|
|
4
|
-
* Generates normalized embeddings for semantic search. The embedding
|
|
5
|
-
* dimension is derived at runtime from the model's actual output.
|
|
6
|
-
* First use downloads the model (~23MB) to the configured cache directory.
|
|
7
|
-
*/
|
|
8
|
-
import type { IndexProgressCallback } from "./types.js";
|
|
9
|
-
export interface EmbedderOptions {
|
|
10
|
-
/** Absolute path to the model cache directory (e.g. ~/.dreb/agent/models/). */
|
|
11
|
-
modelCacheDir: string;
|
|
12
|
-
/** HuggingFace model name. Default: 'Xenova/all-MiniLM-L6-v2'. */
|
|
13
|
-
modelName?: string;
|
|
14
|
-
/** Number of texts to embed per batch. Default: 32. */
|
|
15
|
-
batchSize?: number;
|
|
16
|
-
}
|
|
17
|
-
export declare class Embedder {
|
|
18
|
-
private readonly modelCacheDir;
|
|
19
|
-
private readonly modelName;
|
|
20
|
-
private readonly batchSize;
|
|
21
|
-
private extractor;
|
|
22
|
-
private resolvedDimension;
|
|
23
|
-
constructor(options: EmbedderOptions);
|
|
24
|
-
/**
|
|
25
|
-
* Initialize the model pipeline. Must be called before embedding.
|
|
26
|
-
*
|
|
27
|
-
* On first use this downloads the ONNX model to `modelCacheDir`.
|
|
28
|
-
* Subsequent calls reuse the cached model.
|
|
29
|
-
*/
|
|
30
|
-
initialize(): Promise<void>;
|
|
31
|
-
/**
|
|
32
|
-
* Embed documents for indexing.
|
|
33
|
-
*
|
|
34
|
-
* Applies model-specific prefixes if required, then processes texts
|
|
35
|
-
* in batches of `batchSize` for memory efficiency.
|
|
36
|
-
*/
|
|
37
|
-
embedDocuments(texts: string[], onProgress?: IndexProgressCallback): Promise<Float32Array[]>;
|
|
38
|
-
/**
|
|
39
|
-
* Embed a query for search.
|
|
40
|
-
*
|
|
41
|
-
* Applies model-specific query prefix if required.
|
|
42
|
-
*/
|
|
43
|
-
embedQuery(query: string): Promise<Float32Array>;
|
|
44
|
-
/** Get the embedding dimension. Returns the model's actual dimension once known, or 384 as default. */
|
|
45
|
-
get dimension(): number;
|
|
46
|
-
/** Dispose the pipeline to free memory. */
|
|
47
|
-
dispose(): void;
|
|
48
|
-
/** Throw if initialize() hasn't been called yet. */
|
|
49
|
-
private ensureInitialized;
|
|
50
|
-
}
|
|
51
|
-
//# sourceMappingURL=embedder.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"embedder.d.ts","sourceRoot":"","sources":["../../../src/core/search/embedder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAMxD,MAAM,WAAW,eAAe;IAC/B,+EAA+E;IAC/E,aAAa,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAsBD,qBAAa,QAAQ;IACpB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,iBAAiB,CAAuB;IAEhD,YAAY,OAAO,EAAE,eAAe,EAInC;IAED;;;;;OAKG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CA2BhC;IAED;;;;;OAKG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAiCjG;IAED;;;;OAIG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAgBrD;IAED,uGAAuG;IACvG,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,2CAA2C;IAC3C,OAAO,IAAI,IAAI,CAQd;IAED,oDAAoD;IACpD,OAAO,CAAC,iBAAiB;CAKzB","sourcesContent":["/**\n * Embedding pipeline using @huggingface/transformers.\n *\n * Generates normalized embeddings for semantic search. The embedding\n * dimension is derived at runtime from the model's actual output.\n * First use downloads the model (~23MB) to the configured cache directory.\n */\n\nimport type { IndexProgressCallback } from \"./types.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface EmbedderOptions {\n\t/** Absolute path to the model cache directory (e.g. ~/.dreb/agent/models/). */\n\tmodelCacheDir: string;\n\t/** HuggingFace model name. Default: 'Xenova/all-MiniLM-L6-v2'. */\n\tmodelName?: string;\n\t/** Number of texts to embed per batch. Default: 32. */\n\tbatchSize?: number;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MODEL_NAME = \"Xenova/all-MiniLM-L6-v2\";\nconst DEFAULT_BATCH_SIZE = 32;\nconst DEFAULT_DIMENSION = 384;\n\n/**\n * Model-specific prefixes for document vs query embeddings.\n * nomic-embed-text-v1.5 requires these; most other models don't.\n */\nconst MODEL_PREFIXES: Record<string, { document: string; query: string }> = {\n\t\"nomic-ai/nomic-embed-text-v1.5\": { document: \"search_document: \", query: \"search_query: \" },\n};\n\n// ============================================================================\n// Embedder\n// ============================================================================\n\nexport class Embedder {\n\tprivate readonly modelCacheDir: string;\n\tprivate readonly modelName: string;\n\tprivate readonly batchSize: number;\n\tprivate extractor: any | null = null;\n\tprivate resolvedDimension: number | null = null;\n\n\tconstructor(options: EmbedderOptions) {\n\t\tthis.modelCacheDir = options.modelCacheDir;\n\t\tthis.modelName = options.modelName ?? DEFAULT_MODEL_NAME;\n\t\tthis.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n\t}\n\n\t/**\n\t * Initialize the model pipeline. Must be called before embedding.\n\t *\n\t * On first use this downloads the ONNX model to `modelCacheDir`.\n\t * Subsequent calls reuse the cached model.\n\t */\n\tasync initialize(): Promise<void> {\n\t\tif (this.extractor) return;\n\n\t\t// Dynamic import — @huggingface/transformers is a heavy dependency\n\t\tconst { pipeline, env } = await import(\"@huggingface/transformers\");\n\n\t\t// Direct the model cache to our managed directory\n\t\tenv.cacheDir = this.modelCacheDir;\n\n\t\t// Suppress the onnxruntime native addon warning — WASM fallback is fine\n\t\t// The library tries to load native onnxruntime first and logs a warning\n\t\t// when it falls back to WASM. We suppress this to avoid confusing users.\n\t\tconst originalWarn = console.warn;\n\t\tconsole.warn = (...args: any[]) => {\n\t\t\tconst msg = typeof args[0] === \"string\" ? args[0] : \"\";\n\t\t\tif (msg.includes(\"onnxruntime\") || msg.includes(\"ONNX\")) return;\n\t\t\toriginalWarn.apply(console, args);\n\t\t};\n\n\t\ttry {\n\t\t\tthis.extractor = await pipeline(\"feature-extraction\", this.modelName, {\n\t\t\t\tdtype: \"q8\" as any,\n\t\t\t\tdevice: \"cpu\" as any,\n\t\t\t});\n\t\t} finally {\n\t\t\tconsole.warn = originalWarn;\n\t\t}\n\t}\n\n\t/**\n\t * Embed documents for indexing.\n\t *\n\t * Applies model-specific prefixes if required, then processes texts\n\t * in batches of `batchSize` for memory efficiency.\n\t */\n\tasync embedDocuments(texts: string[], onProgress?: IndexProgressCallback): Promise<Float32Array[]> {\n\t\tthis.ensureInitialized();\n\n\t\tconst results: Float32Array[] = [];\n\t\tconst total = texts.length;\n\t\tconst prefix = MODEL_PREFIXES[this.modelName]?.document ?? \"\";\n\n\t\tfor (let i = 0; i < total; i += this.batchSize) {\n\t\t\tconst batch = texts.slice(i, i + this.batchSize);\n\t\t\tconst prefixed = prefix ? batch.map((t) => prefix + t) : batch;\n\n\t\t\tconst output = await this.extractor!(prefixed, {\n\t\t\t\tpooling: \"mean\",\n\t\t\t\tnormalize: true,\n\t\t\t});\n\n\t\t\t// output.data is a flat Float32Array of shape [batchLen, dim]\n\t\t\tconst data: Float32Array = output.data;\n\t\t\tconst dim = data.length / batch.length;\n\t\t\tif (this.resolvedDimension === null) {\n\t\t\t\tthis.resolvedDimension = dim;\n\t\t\t}\n\t\t\tfor (let j = 0; j < batch.length; j++) {\n\t\t\t\tconst start = j * dim;\n\t\t\t\tresults.push(data.slice(start, start + dim));\n\t\t\t}\n\n\t\t\tif (onProgress) {\n\t\t\t\tonProgress(\"embedding\", Math.min(i + batch.length, total), total);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Embed a query for search.\n\t *\n\t * Applies model-specific query prefix if required.\n\t */\n\tasync embedQuery(query: string): Promise<Float32Array> {\n\t\tthis.ensureInitialized();\n\n\t\tconst prefix = MODEL_PREFIXES[this.modelName]?.query ?? \"\";\n\t\tconst output = await this.extractor!(prefix + query, {\n\t\t\tpooling: \"mean\",\n\t\t\tnormalize: true,\n\t\t});\n\n\t\t// Single input — derive dimension and slice to exactly one vector\n\t\tconst data: Float32Array = output.data;\n\t\tconst dim = data.length;\n\t\tif (this.resolvedDimension === null) {\n\t\t\tthis.resolvedDimension = dim;\n\t\t}\n\t\treturn data.slice(0, dim);\n\t}\n\n\t/** Get the embedding dimension. Returns the model's actual dimension once known, or 384 as default. */\n\tget dimension(): number {\n\t\treturn this.resolvedDimension ?? DEFAULT_DIMENSION;\n\t}\n\n\t/** Dispose the pipeline to free memory. */\n\tdispose(): void {\n\t\tif (this.extractor) {\n\t\t\t// The pipeline object may have a dispose method depending on the version\n\t\t\tif (typeof this.extractor.dispose === \"function\") {\n\t\t\t\tthis.extractor.dispose();\n\t\t\t}\n\t\t\tthis.extractor = null;\n\t\t}\n\t}\n\n\t/** Throw if initialize() hasn't been called yet. */\n\tprivate ensureInitialized(): void {\n\t\tif (!this.extractor) {\n\t\t\tthrow new Error(\"Embedder not initialized. Call initialize() first.\");\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Embedding pipeline using @huggingface/transformers.
|
|
3
|
-
*
|
|
4
|
-
* Generates normalized embeddings for semantic search. The embedding
|
|
5
|
-
* dimension is derived at runtime from the model's actual output.
|
|
6
|
-
* First use downloads the model (~23MB) to the configured cache directory.
|
|
7
|
-
*/
|
|
8
|
-
// ============================================================================
|
|
9
|
-
// Constants
|
|
10
|
-
// ============================================================================
|
|
11
|
-
const DEFAULT_MODEL_NAME = "Xenova/all-MiniLM-L6-v2";
|
|
12
|
-
const DEFAULT_BATCH_SIZE = 32;
|
|
13
|
-
const DEFAULT_DIMENSION = 384;
|
|
14
|
-
/**
|
|
15
|
-
* Model-specific prefixes for document vs query embeddings.
|
|
16
|
-
* nomic-embed-text-v1.5 requires these; most other models don't.
|
|
17
|
-
*/
|
|
18
|
-
const MODEL_PREFIXES = {
|
|
19
|
-
"nomic-ai/nomic-embed-text-v1.5": { document: "search_document: ", query: "search_query: " },
|
|
20
|
-
};
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// Embedder
|
|
23
|
-
// ============================================================================
|
|
24
|
-
export class Embedder {
|
|
25
|
-
modelCacheDir;
|
|
26
|
-
modelName;
|
|
27
|
-
batchSize;
|
|
28
|
-
extractor = null;
|
|
29
|
-
resolvedDimension = null;
|
|
30
|
-
constructor(options) {
|
|
31
|
-
this.modelCacheDir = options.modelCacheDir;
|
|
32
|
-
this.modelName = options.modelName ?? DEFAULT_MODEL_NAME;
|
|
33
|
-
this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Initialize the model pipeline. Must be called before embedding.
|
|
37
|
-
*
|
|
38
|
-
* On first use this downloads the ONNX model to `modelCacheDir`.
|
|
39
|
-
* Subsequent calls reuse the cached model.
|
|
40
|
-
*/
|
|
41
|
-
async initialize() {
|
|
42
|
-
if (this.extractor)
|
|
43
|
-
return;
|
|
44
|
-
// Dynamic import — @huggingface/transformers is a heavy dependency
|
|
45
|
-
const { pipeline, env } = await import("@huggingface/transformers");
|
|
46
|
-
// Direct the model cache to our managed directory
|
|
47
|
-
env.cacheDir = this.modelCacheDir;
|
|
48
|
-
// Suppress the onnxruntime native addon warning — WASM fallback is fine
|
|
49
|
-
// The library tries to load native onnxruntime first and logs a warning
|
|
50
|
-
// when it falls back to WASM. We suppress this to avoid confusing users.
|
|
51
|
-
const originalWarn = console.warn;
|
|
52
|
-
console.warn = (...args) => {
|
|
53
|
-
const msg = typeof args[0] === "string" ? args[0] : "";
|
|
54
|
-
if (msg.includes("onnxruntime") || msg.includes("ONNX"))
|
|
55
|
-
return;
|
|
56
|
-
originalWarn.apply(console, args);
|
|
57
|
-
};
|
|
58
|
-
try {
|
|
59
|
-
this.extractor = await pipeline("feature-extraction", this.modelName, {
|
|
60
|
-
dtype: "q8",
|
|
61
|
-
device: "cpu",
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
finally {
|
|
65
|
-
console.warn = originalWarn;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Embed documents for indexing.
|
|
70
|
-
*
|
|
71
|
-
* Applies model-specific prefixes if required, then processes texts
|
|
72
|
-
* in batches of `batchSize` for memory efficiency.
|
|
73
|
-
*/
|
|
74
|
-
async embedDocuments(texts, onProgress) {
|
|
75
|
-
this.ensureInitialized();
|
|
76
|
-
const results = [];
|
|
77
|
-
const total = texts.length;
|
|
78
|
-
const prefix = MODEL_PREFIXES[this.modelName]?.document ?? "";
|
|
79
|
-
for (let i = 0; i < total; i += this.batchSize) {
|
|
80
|
-
const batch = texts.slice(i, i + this.batchSize);
|
|
81
|
-
const prefixed = prefix ? batch.map((t) => prefix + t) : batch;
|
|
82
|
-
const output = await this.extractor(prefixed, {
|
|
83
|
-
pooling: "mean",
|
|
84
|
-
normalize: true,
|
|
85
|
-
});
|
|
86
|
-
// output.data is a flat Float32Array of shape [batchLen, dim]
|
|
87
|
-
const data = output.data;
|
|
88
|
-
const dim = data.length / batch.length;
|
|
89
|
-
if (this.resolvedDimension === null) {
|
|
90
|
-
this.resolvedDimension = dim;
|
|
91
|
-
}
|
|
92
|
-
for (let j = 0; j < batch.length; j++) {
|
|
93
|
-
const start = j * dim;
|
|
94
|
-
results.push(data.slice(start, start + dim));
|
|
95
|
-
}
|
|
96
|
-
if (onProgress) {
|
|
97
|
-
onProgress("embedding", Math.min(i + batch.length, total), total);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return results;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Embed a query for search.
|
|
104
|
-
*
|
|
105
|
-
* Applies model-specific query prefix if required.
|
|
106
|
-
*/
|
|
107
|
-
async embedQuery(query) {
|
|
108
|
-
this.ensureInitialized();
|
|
109
|
-
const prefix = MODEL_PREFIXES[this.modelName]?.query ?? "";
|
|
110
|
-
const output = await this.extractor(prefix + query, {
|
|
111
|
-
pooling: "mean",
|
|
112
|
-
normalize: true,
|
|
113
|
-
});
|
|
114
|
-
// Single input — derive dimension and slice to exactly one vector
|
|
115
|
-
const data = output.data;
|
|
116
|
-
const dim = data.length;
|
|
117
|
-
if (this.resolvedDimension === null) {
|
|
118
|
-
this.resolvedDimension = dim;
|
|
119
|
-
}
|
|
120
|
-
return data.slice(0, dim);
|
|
121
|
-
}
|
|
122
|
-
/** Get the embedding dimension. Returns the model's actual dimension once known, or 384 as default. */
|
|
123
|
-
get dimension() {
|
|
124
|
-
return this.resolvedDimension ?? DEFAULT_DIMENSION;
|
|
125
|
-
}
|
|
126
|
-
/** Dispose the pipeline to free memory. */
|
|
127
|
-
dispose() {
|
|
128
|
-
if (this.extractor) {
|
|
129
|
-
// The pipeline object may have a dispose method depending on the version
|
|
130
|
-
if (typeof this.extractor.dispose === "function") {
|
|
131
|
-
this.extractor.dispose();
|
|
132
|
-
}
|
|
133
|
-
this.extractor = null;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/** Throw if initialize() hasn't been called yet. */
|
|
137
|
-
ensureInitialized() {
|
|
138
|
-
if (!this.extractor) {
|
|
139
|
-
throw new Error("Embedder not initialized. Call initialize() first.");
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
//# sourceMappingURL=embedder.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"embedder.js","sourceRoot":"","sources":["../../../src/core/search/embedder.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Embedding pipeline using @huggingface/transformers.\n *\n * Generates normalized embeddings for semantic search. The embedding\n * dimension is derived at runtime from the model's actual output.\n * First use downloads the model (~23MB) to the configured cache directory.\n */\n\nimport type { IndexProgressCallback } from \"./types.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface EmbedderOptions {\n\t/** Absolute path to the model cache directory (e.g. ~/.dreb/agent/models/). */\n\tmodelCacheDir: string;\n\t/** HuggingFace model name. Default: 'Xenova/all-MiniLM-L6-v2'. */\n\tmodelName?: string;\n\t/** Number of texts to embed per batch. Default: 32. */\n\tbatchSize?: number;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MODEL_NAME = \"Xenova/all-MiniLM-L6-v2\";\nconst DEFAULT_BATCH_SIZE = 32;\nconst DEFAULT_DIMENSION = 384;\n\n/**\n * Model-specific prefixes for document vs query embeddings.\n * nomic-embed-text-v1.5 requires these; most other models don't.\n */\nconst MODEL_PREFIXES: Record<string, { document: string; query: string }> = {\n\t\"nomic-ai/nomic-embed-text-v1.5\": { document: \"search_document: \", query: \"search_query: \" },\n};\n\n// ============================================================================\n// Embedder\n// ============================================================================\n\nexport class Embedder {\n\tprivate readonly modelCacheDir: string;\n\tprivate readonly modelName: string;\n\tprivate readonly batchSize: number;\n\tprivate extractor: any | null = null;\n\tprivate resolvedDimension: number | null = null;\n\n\tconstructor(options: EmbedderOptions) {\n\t\tthis.modelCacheDir = options.modelCacheDir;\n\t\tthis.modelName = options.modelName ?? DEFAULT_MODEL_NAME;\n\t\tthis.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n\t}\n\n\t/**\n\t * Initialize the model pipeline. Must be called before embedding.\n\t *\n\t * On first use this downloads the ONNX model to `modelCacheDir`.\n\t * Subsequent calls reuse the cached model.\n\t */\n\tasync initialize(): Promise<void> {\n\t\tif (this.extractor) return;\n\n\t\t// Dynamic import — @huggingface/transformers is a heavy dependency\n\t\tconst { pipeline, env } = await import(\"@huggingface/transformers\");\n\n\t\t// Direct the model cache to our managed directory\n\t\tenv.cacheDir = this.modelCacheDir;\n\n\t\t// Suppress the onnxruntime native addon warning — WASM fallback is fine\n\t\t// The library tries to load native onnxruntime first and logs a warning\n\t\t// when it falls back to WASM. We suppress this to avoid confusing users.\n\t\tconst originalWarn = console.warn;\n\t\tconsole.warn = (...args: any[]) => {\n\t\t\tconst msg = typeof args[0] === \"string\" ? args[0] : \"\";\n\t\t\tif (msg.includes(\"onnxruntime\") || msg.includes(\"ONNX\")) return;\n\t\t\toriginalWarn.apply(console, args);\n\t\t};\n\n\t\ttry {\n\t\t\tthis.extractor = await pipeline(\"feature-extraction\", this.modelName, {\n\t\t\t\tdtype: \"q8\" as any,\n\t\t\t\tdevice: \"cpu\" as any,\n\t\t\t});\n\t\t} finally {\n\t\t\tconsole.warn = originalWarn;\n\t\t}\n\t}\n\n\t/**\n\t * Embed documents for indexing.\n\t *\n\t * Applies model-specific prefixes if required, then processes texts\n\t * in batches of `batchSize` for memory efficiency.\n\t */\n\tasync embedDocuments(texts: string[], onProgress?: IndexProgressCallback): Promise<Float32Array[]> {\n\t\tthis.ensureInitialized();\n\n\t\tconst results: Float32Array[] = [];\n\t\tconst total = texts.length;\n\t\tconst prefix = MODEL_PREFIXES[this.modelName]?.document ?? \"\";\n\n\t\tfor (let i = 0; i < total; i += this.batchSize) {\n\t\t\tconst batch = texts.slice(i, i + this.batchSize);\n\t\t\tconst prefixed = prefix ? batch.map((t) => prefix + t) : batch;\n\n\t\t\tconst output = await this.extractor!(prefixed, {\n\t\t\t\tpooling: \"mean\",\n\t\t\t\tnormalize: true,\n\t\t\t});\n\n\t\t\t// output.data is a flat Float32Array of shape [batchLen, dim]\n\t\t\tconst data: Float32Array = output.data;\n\t\t\tconst dim = data.length / batch.length;\n\t\t\tif (this.resolvedDimension === null) {\n\t\t\t\tthis.resolvedDimension = dim;\n\t\t\t}\n\t\t\tfor (let j = 0; j < batch.length; j++) {\n\t\t\t\tconst start = j * dim;\n\t\t\t\tresults.push(data.slice(start, start + dim));\n\t\t\t}\n\n\t\t\tif (onProgress) {\n\t\t\t\tonProgress(\"embedding\", Math.min(i + batch.length, total), total);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Embed a query for search.\n\t *\n\t * Applies model-specific query prefix if required.\n\t */\n\tasync embedQuery(query: string): Promise<Float32Array> {\n\t\tthis.ensureInitialized();\n\n\t\tconst prefix = MODEL_PREFIXES[this.modelName]?.query ?? \"\";\n\t\tconst output = await this.extractor!(prefix + query, {\n\t\t\tpooling: \"mean\",\n\t\t\tnormalize: true,\n\t\t});\n\n\t\t// Single input — derive dimension and slice to exactly one vector\n\t\tconst data: Float32Array = output.data;\n\t\tconst dim = data.length;\n\t\tif (this.resolvedDimension === null) {\n\t\t\tthis.resolvedDimension = dim;\n\t\t}\n\t\treturn data.slice(0, dim);\n\t}\n\n\t/** Get the embedding dimension. Returns the model's actual dimension once known, or 384 as default. */\n\tget dimension(): number {\n\t\treturn this.resolvedDimension ?? DEFAULT_DIMENSION;\n\t}\n\n\t/** Dispose the pipeline to free memory. */\n\tdispose(): void {\n\t\tif (this.extractor) {\n\t\t\t// The pipeline object may have a dispose method depending on the version\n\t\t\tif (typeof this.extractor.dispose === \"function\") {\n\t\t\t\tthis.extractor.dispose();\n\t\t\t}\n\t\t\tthis.extractor = null;\n\t\t}\n\t}\n\n\t/** Throw if initialize() hasn't been called yet. */\n\tprivate ensureInitialized(): void {\n\t\tif (!this.extractor) {\n\t\t\tthrow new Error(\"Embedder not initialized. Call initialize() first.\");\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Index lifecycle manager.
|
|
3
|
-
*
|
|
4
|
-
* Orchestrates: file scanning → chunking → embedding → FTS indexing → import graph.
|
|
5
|
-
* Supports incremental updates via mtime comparison.
|
|
6
|
-
*/
|
|
7
|
-
import type { SearchDatabase } from "./db.js";
|
|
8
|
-
import type { Embedder } from "./embedder.js";
|
|
9
|
-
import type { IndexConfig, IndexProgressCallback } from "./types.js";
|
|
10
|
-
export declare class IndexManager {
|
|
11
|
-
private readonly config;
|
|
12
|
-
private db;
|
|
13
|
-
private embedder;
|
|
14
|
-
constructor(config: IndexConfig);
|
|
15
|
-
/** Check if node:sqlite is available. */
|
|
16
|
-
static isAvailable(): boolean;
|
|
17
|
-
/** Open or create the index database. */
|
|
18
|
-
open(): SearchDatabase;
|
|
19
|
-
/**
|
|
20
|
-
* Set an external embedder instance to share with the caller.
|
|
21
|
-
* When set, embedChunks() uses this instead of creating its own.
|
|
22
|
-
*/
|
|
23
|
-
setEmbedder(embedder: Embedder): void;
|
|
24
|
-
/** Close the database and dispose resources. */
|
|
25
|
-
close(): void;
|
|
26
|
-
/** Get the database, opening if needed. */
|
|
27
|
-
getDb(): SearchDatabase;
|
|
28
|
-
/**
|
|
29
|
-
* Build or incrementally update the index.
|
|
30
|
-
*
|
|
31
|
-
* 1. Scan project for files
|
|
32
|
-
* 2. Compare mtimes against stored records
|
|
33
|
-
* 3. Re-chunk and re-embed only changed/new files
|
|
34
|
-
* 4. Remove deleted files
|
|
35
|
-
*/
|
|
36
|
-
buildIndex(onProgress?: IndexProgressCallback): Promise<{
|
|
37
|
-
added: number;
|
|
38
|
-
updated: number;
|
|
39
|
-
removed: number;
|
|
40
|
-
failed: number;
|
|
41
|
-
}>;
|
|
42
|
-
/**
|
|
43
|
-
* Ensure all chunks have embeddings, generating any missing ones.
|
|
44
|
-
*/
|
|
45
|
-
ensureEmbeddings(onProgress?: IndexProgressCallback): Promise<number>;
|
|
46
|
-
private embedChunks;
|
|
47
|
-
/** Check if the index exists. */
|
|
48
|
-
indexExists(): boolean;
|
|
49
|
-
/** Get index stats. */
|
|
50
|
-
getStats(): {
|
|
51
|
-
files: number;
|
|
52
|
-
chunks: number;
|
|
53
|
-
} | null;
|
|
54
|
-
}
|
|
55
|
-
//# sourceMappingURL=index-manager.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-manager.d.ts","sourceRoot":"","sources":["../../../src/core/search/index-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,WAAW,EAAe,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAYlF,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,EAAE,CAA+B;IACzC,OAAO,CAAC,QAAQ,CAAyB;IAEzC,YAAY,MAAM,EAAE,WAAW,EAE9B;IAED,yCAAyC;IACzC,MAAM,CAAC,WAAW,IAAI,OAAO,CAE5B;IAED,yCAAyC;IACzC,IAAI,IAAI,cAAc,CAOrB;IAED;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAEpC;IAED,gDAAgD;IAChD,KAAK,IAAI,IAAI,CAQZ;IAED,2CAA2C;IAC3C,KAAK,IAAI,cAAc,CAGtB;IAED;;;;;;;OAOG;IACG,UAAU,CACf,UAAU,CAAC,EAAE,qBAAqB,GAChC,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CA8H9E;IAED;;OAEG;IACG,gBAAgB,CAAC,UAAU,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAO1E;YAMa,WAAW;IA2BzB,iCAAiC;IACjC,WAAW,IAAI,OAAO,CAGrB;IAED,uBAAuB;IACvB,QAAQ,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAOnD;CACD","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);\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"]}
|