@dreb/semantic-search 2.4.3 → 2.4.4
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 +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +1 -0
- package/dist/db.js.map +1 -1
- package/dist/scanner.js +9 -2
- package/dist/scanner.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "semantic-search",
|
|
3
3
|
"description": "Semantic codebase search — natural language queries over code and docs using embeddings, tree-sitter parsing, and POEM multi-signal ranking",
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.4",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Drew Brereton"
|
|
7
7
|
},
|
package/dist/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAUhF,wEAAwE;AACxE,wBAAgB,iBAAiB,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
package/dist/db.js
CHANGED
package/dist/db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/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
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/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/* node:sqlite not available (requires Node 22.5+) — gracefully degrade */\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"]}
|
package/dist/scanner.js
CHANGED
|
@@ -122,6 +122,7 @@ function isHomeDirPath(dir) {
|
|
|
122
122
|
return normalizedDir === normalizedHome;
|
|
123
123
|
}
|
|
124
124
|
catch {
|
|
125
|
+
/* os.homedir() can throw in sandboxed/unusual environments */
|
|
125
126
|
return false;
|
|
126
127
|
}
|
|
127
128
|
}
|
|
@@ -136,6 +137,7 @@ function scanShallow(dir, results) {
|
|
|
136
137
|
entries = readdirSync(dir);
|
|
137
138
|
}
|
|
138
139
|
catch {
|
|
140
|
+
/* Directory unreadable (EACCES, ENOENT) — abandon shallow scan */
|
|
139
141
|
return;
|
|
140
142
|
}
|
|
141
143
|
for (const entry of entries) {
|
|
@@ -148,6 +150,7 @@ function scanShallow(dir, results) {
|
|
|
148
150
|
stats = statSync(fullPath);
|
|
149
151
|
}
|
|
150
152
|
catch {
|
|
153
|
+
/* Broken symlink or race deletion — skip entry */
|
|
151
154
|
continue;
|
|
152
155
|
}
|
|
153
156
|
// Only index files, not directories (shallow mode)
|
|
@@ -240,7 +243,8 @@ function walkDirectory(dir, root, ig, results) {
|
|
|
240
243
|
entries = readdirSync(dir);
|
|
241
244
|
}
|
|
242
245
|
catch {
|
|
243
|
-
|
|
246
|
+
/* Directory unreadable (permission denied, etc.) — stop recursion into this branch */
|
|
247
|
+
return;
|
|
244
248
|
}
|
|
245
249
|
for (const entry of entries) {
|
|
246
250
|
const fullPath = join(dir, entry);
|
|
@@ -251,7 +255,8 @@ function walkDirectory(dir, root, ig, results) {
|
|
|
251
255
|
stats = statSync(fullPath);
|
|
252
256
|
}
|
|
253
257
|
catch {
|
|
254
|
-
|
|
258
|
+
/* Broken symlink or race deletion — skip entry */
|
|
259
|
+
continue;
|
|
255
260
|
}
|
|
256
261
|
if (stats.isDirectory()) {
|
|
257
262
|
// Hard-coded skip list
|
|
@@ -302,6 +307,7 @@ function scanMemoryDir(memoryDir, projectRoot, results, baseMemoryDir) {
|
|
|
302
307
|
entries = readdirSync(memoryDir);
|
|
303
308
|
}
|
|
304
309
|
catch {
|
|
310
|
+
/* Memory directory doesn't exist or is unreadable — skip */
|
|
305
311
|
return;
|
|
306
312
|
}
|
|
307
313
|
for (const entry of entries) {
|
|
@@ -311,6 +317,7 @@ function scanMemoryDir(memoryDir, projectRoot, results, baseMemoryDir) {
|
|
|
311
317
|
stats = statSync(fullPath);
|
|
312
318
|
}
|
|
313
319
|
catch {
|
|
320
|
+
/* Broken symlink or race deletion — skip entry */
|
|
314
321
|
continue;
|
|
315
322
|
}
|
|
316
323
|
if (stats.isDirectory()) {
|
package/dist/scanner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * File scanner for the semantic search subsystem.\n *\n * Discovers project files for indexing by walking the directory tree,\n * respecting .gitignore rules, and classifying files by type.\n */\n\nimport { existsSync, readdirSync, readFileSync, type Stats, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { extname, isAbsolute, join, relative, sep } from \"node:path\";\nimport ignore from \"ignore\";\nimport type { FileType } from \"./types.js\";\n\n// ============================================================================\n// Public types\n// ============================================================================\n\n/** A file discovered by the scanner, ready for indexing. */\nexport interface ScannedFile {\n\t/** Path relative to the project root (posix separators). */\n\tfilePath: string;\n\t/** Detected file type. */\n\tfileType: FileType;\n\t/** File modification time in milliseconds since epoch. */\n\tmtime: number;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Maximum file size to index (1 MB). */\nconst MAX_FILE_SIZE = 1024 * 1024;\n\n/** Directories unconditionally skipped during traversal. */\nconst SKIP_DIRS = new Set([\n\t\"node_modules\",\n\t\".git\",\n\t\".dreb/index\",\n\t\".hg\",\n\t\".svn\",\n\t\"__pycache__\",\n\t\".tox\",\n\t\".venv\",\n\t\"dist\",\n\t\"build\",\n\t\".next\",\n\t\".nuxt\",\n\t\"coverage\",\n\t\".cache\",\n]);\n\n/** Extension → FileType mapping. */\nconst EXTENSION_MAP: ReadonlyMap<string, FileType> = new Map<string, FileType>([\n\t// Tree-sitter languages\n\t[\".ts\", \"typescript\"],\n\t[\".tsx\", \"tsx\"],\n\t[\".js\", \"javascript\"],\n\t[\".mjs\", \"javascript\"],\n\t[\".cjs\", \"javascript\"],\n\t[\".py\", \"python\"],\n\t[\".go\", \"go\"],\n\t[\".rs\", \"rust\"],\n\t[\".java\", \"java\"],\n\t[\".c\", \"c\"],\n\t[\".h\", \"c\"],\n\t[\".cpp\", \"cpp\"],\n\t[\".hpp\", \"cpp\"],\n\t[\".cc\", \"cpp\"],\n\t[\".cxx\", \"cpp\"],\n\t[\".hh\", \"cpp\"],\n\t[\".hxx\", \"cpp\"],\n\t// Text file types\n\t[\".md\", \"markdown\"],\n\t[\".mdx\", \"markdown\"],\n\t[\".yml\", \"yaml\"],\n\t[\".yaml\", \"yaml\"],\n\t[\".json\", \"json\"],\n\t[\".toml\", \"toml\"],\n\t[\".txt\", \"plaintext\"],\n\t[\".cfg\", \"plaintext\"],\n\t[\".ini\", \"plaintext\"],\n\t[\".env\", \"plaintext\"],\n\t[\".conf\", \"plaintext\"],\n]);\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Detect the {@link FileType} for a file path based on its extension.\n * Returns `null` for unrecognized extensions or files without an extension.\n */\nexport function detectFileType(filePath: string): FileType | null {\n\tconst ext = extname(filePath).toLowerCase();\n\tif (!ext) return null;\n\treturn EXTENSION_MAP.get(ext) ?? null;\n}\n\n/**\n * Scan a project directory and return all indexable files.\n *\n * Walks the tree rooted at {@link projectRoot}, respects `.gitignore` rules,\n * skips binary / oversized files, and optionally includes memory files from\n * a global memory directory.\n */\nexport async function scanProject(\n\tprojectRoot: string,\n\tglobalMemoryDir?: string,\n\tvisibleDirs?: string[],\n): Promise<ScannedFile[]> {\n\tconst results: ScannedFile[] = [];\n\n\t// Detect if projectRoot is the home directory — use shallow scan mode\n\t// to avoid recursing into the entire home dir (which would be catastrophic).\n\tconst isHomeDir = isHomeDirPath(projectRoot);\n\n\tif (isHomeDir) {\n\t\t// Shallow mode: only scan top-level files and ~/.dreb/memory/\n\t\tscanShallow(projectRoot, results);\n\t} else {\n\t\t// Normal mode: full recursive walk with .gitignore\n\t\tconst ig = ignore();\n\t\tloadGitignore(ig, projectRoot, projectRoot);\n\t\twalkDirectory(projectRoot, projectRoot, ig, results);\n\t}\n\n\t// Include tool-visible .dreb/ subdirs (bypasses gitignore).\n\t// In home dir mode, global memory is already handled separately below,\n\t// and we don't want to double-scan ~/.dreb/memory/.\n\tif (!isHomeDir) {\n\t\tfor (const dir of visibleDirs ?? []) {\n\t\t\tscanMemoryDir(dir, projectRoot, results);\n\t\t}\n\t}\n\n\t// Include global memory files if the directory exists\n\tif (globalMemoryDir && existsSync(globalMemoryDir)) {\n\t\tscanMemoryDir(globalMemoryDir, projectRoot, results);\n\t}\n\n\treturn results;\n}\n\n/** Check if a path is the user's home directory. */\nfunction isHomeDirPath(dir: string): boolean {\n\ttry {\n\t\tconst home = homedir();\n\t\t// Normalize trailing slashes for comparison\n\t\tconst normalizedDir = dir.replace(/[/\\\\]+$/, \"\");\n\t\tconst normalizedHome = home.replace(/[/\\\\]+$/, \"\");\n\t\treturn normalizedDir === normalizedHome;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Shallow scan mode for home directory: only index top-level files\n * (no directory recursion) to avoid scanning the entire home directory.\n * Memory files are handled separately via scanMemoryDir.\n */\nfunction scanShallow(dir: string, results: ScannedFile[]): void {\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(dir);\n\t} catch {\n\t\treturn;\n\t}\n\n\tfor (const entry of entries) {\n\t\t// Skip dotfiles/dotdirs in home dir (except specific ones we want)\n\t\tif (entry.startsWith(\".\")) continue;\n\n\t\tconst fullPath = join(dir, entry);\n\n\t\tlet stats: Stats;\n\t\ttry {\n\t\t\tstats = statSync(fullPath);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Only index files, not directories (shallow mode)\n\t\tif (!stats.isFile()) continue;\n\t\tif (stats.size > MAX_FILE_SIZE) continue;\n\t\tif (stats.size === 0) continue;\n\n\t\tconst fileType = detectFileType(entry);\n\t\tif (!fileType) continue;\n\n\t\tresults.push({\n\t\t\tfilePath: entry,\n\t\t\tfileType,\n\t\t\tmtime: stats.mtimeMs,\n\t\t});\n\t}\n}\n\n// ============================================================================\n// Internal helpers\n// ============================================================================\n\ntype IgnoreMatcher = ReturnType<typeof ignore>;\n\n/** Convert an OS path to posix separators for ignore matching. */\nfunction toPosix(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\n/** Load .gitignore rules from a directory into the ignore matcher. */\nfunction loadGitignore(ig: IgnoreMatcher, dir: string, root: string): void {\n\tconst gitignorePath = join(dir, \".gitignore\");\n\tif (!existsSync(gitignorePath)) return;\n\n\ttry {\n\t\tconst content = readFileSync(gitignorePath, \"utf-8\");\n\t\tconst relDir = relative(root, dir);\n\t\tconst prefix = relDir ? `${toPosix(relDir)}/` : \"\";\n\n\t\tconst patterns = content\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((line) => prefixPattern(line, prefix))\n\t\t\t.filter((line): line is string => line !== null);\n\n\t\tif (patterns.length > 0) {\n\t\t\tig.add(patterns);\n\t\t}\n\t} catch {\n\t\t// Unreadable .gitignore — skip silently\n\t}\n}\n\n/**\n * Prefix a .gitignore pattern with a directory path so it applies\n * correctly when matching against root-relative paths.\n */\nfunction prefixPattern(line: string, prefix: string): string | null {\n\tconst trimmed = line.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\")) return null;\n\n\tlet pattern = line;\n\tlet negated = false;\n\n\tif (pattern.startsWith(\"!\")) {\n\t\tnegated = true;\n\t\tpattern = pattern.slice(1);\n\t} else if (pattern.startsWith(\"\\\\!\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tconst prefixed = prefix ? `${prefix}${pattern}` : pattern;\n\treturn negated ? `!${prefixed}` : prefixed;\n}\n\n/**\n * Check if a directory component (relative to root) should be unconditionally skipped.\n * Handles both top-level names (\"node_modules\") and nested paths (\".dreb/index\").\n */\nfunction shouldSkipDir(relPath: string): boolean {\n\tconst posix = toPosix(relPath);\n\n\t// Check the directory name itself\n\tconst parts = posix.split(\"/\");\n\tconst name = parts[parts.length - 1];\n\tif (SKIP_DIRS.has(name)) return true;\n\n\t// Check multi-segment skip patterns (e.g. \".dreb/index\")\n\tfor (const skip of SKIP_DIRS) {\n\t\tif (skip.includes(\"/\") && (posix === skip || posix.endsWith(`/${skip}`))) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/** Recursively walk a directory, collecting indexable files. */\nfunction walkDirectory(dir: string, root: string, ig: IgnoreMatcher, results: ScannedFile[]): void {\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(dir);\n\t} catch {\n\t\treturn; // Permission denied, etc.\n\t}\n\n\tfor (const entry of entries) {\n\t\tconst fullPath = join(dir, entry);\n\t\tconst relPath = relative(root, fullPath);\n\t\tconst posixRel = toPosix(relPath);\n\n\t\tlet stats: Stats;\n\t\ttry {\n\t\t\tstats = statSync(fullPath);\n\t\t} catch {\n\t\t\tcontinue; // Broken symlink, etc.\n\t\t}\n\n\t\tif (stats.isDirectory()) {\n\t\t\t// Hard-coded skip list\n\t\t\tif (shouldSkipDir(relPath)) continue;\n\n\t\t\t// .gitignore check (directories need trailing slash)\n\t\t\tif (ig.ignores(`${posixRel}/`)) continue;\n\n\t\t\t// Load nested .gitignore before descending\n\t\t\tloadGitignore(ig, fullPath, root);\n\n\t\t\twalkDirectory(fullPath, root, ig, results);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!stats.isFile()) continue;\n\n\t\t// .gitignore check for files\n\t\tif (ig.ignores(posixRel)) continue;\n\n\t\t// Size gate\n\t\tif (stats.size > MAX_FILE_SIZE) continue;\n\t\tif (stats.size === 0) continue;\n\n\t\t// File type detection\n\t\tconst fileType = detectFileType(entry);\n\t\tif (!fileType) continue;\n\n\t\tresults.push({\n\t\t\tfilePath: posixRel,\n\t\t\tfileType,\n\t\t\tmtime: stats.mtimeMs,\n\t\t});\n\t}\n}\n\n/**\n * Scan a memory directory (project or global) for indexable files.\n *\n * Memory directories are always fully included — no .gitignore filtering —\n * because they live outside the normal project tree or in `.dreb/` which\n * is typically gitignored.\n *\n * Paths for global memory files are stored with a `~memory/` prefix\n * to distinguish them from project files.\n */\nfunction scanMemoryDir(memoryDir: string, projectRoot: string, results: ScannedFile[], baseMemoryDir?: string): void {\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(memoryDir);\n\t} catch {\n\t\treturn;\n\t}\n\n\tfor (const entry of entries) {\n\t\tconst fullPath = join(memoryDir, entry);\n\n\t\tlet stats: Stats;\n\t\ttry {\n\t\t\tstats = statSync(fullPath);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (stats.isDirectory()) {\n\t\t\t// Recurse into subdirectories\n\t\t\tscanMemoryDir(fullPath, projectRoot, results, baseMemoryDir ?? memoryDir);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!stats.isFile()) continue;\n\t\tif (stats.size > MAX_FILE_SIZE) continue;\n\t\tif (stats.size === 0) continue;\n\n\t\tconst fileType = detectFileType(entry);\n\t\tif (!fileType) continue;\n\n\t\t// If the memory dir is inside the project root, use normal relative path.\n\t\t// Otherwise, use a ~memory/ prefix so paths remain unique and identifiable.\n\t\tconst rel = relative(projectRoot, fullPath);\n\t\tconst isOutsideProject = rel.startsWith(\"..\") || isAbsolute(rel);\n\t\tconst rootMemoryDir = baseMemoryDir ?? memoryDir;\n\t\tconst filePath = isOutsideProject ? `~memory/${relative(rootMemoryDir, fullPath)}` : rel;\n\n\t\tresults.push({\n\t\t\tfilePath: toPosix(filePath),\n\t\t\tfileType,\n\t\t\tmtime: stats.mtimeMs,\n\t\t});\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * File scanner for the semantic search subsystem.\n *\n * Discovers project files for indexing by walking the directory tree,\n * respecting .gitignore rules, and classifying files by type.\n */\n\nimport { existsSync, readdirSync, readFileSync, type Stats, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { extname, isAbsolute, join, relative, sep } from \"node:path\";\nimport ignore from \"ignore\";\nimport type { FileType } from \"./types.js\";\n\n// ============================================================================\n// Public types\n// ============================================================================\n\n/** A file discovered by the scanner, ready for indexing. */\nexport interface ScannedFile {\n\t/** Path relative to the project root (posix separators). */\n\tfilePath: string;\n\t/** Detected file type. */\n\tfileType: FileType;\n\t/** File modification time in milliseconds since epoch. */\n\tmtime: number;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** Maximum file size to index (1 MB). */\nconst MAX_FILE_SIZE = 1024 * 1024;\n\n/** Directories unconditionally skipped during traversal. */\nconst SKIP_DIRS = new Set([\n\t\"node_modules\",\n\t\".git\",\n\t\".dreb/index\",\n\t\".hg\",\n\t\".svn\",\n\t\"__pycache__\",\n\t\".tox\",\n\t\".venv\",\n\t\"dist\",\n\t\"build\",\n\t\".next\",\n\t\".nuxt\",\n\t\"coverage\",\n\t\".cache\",\n]);\n\n/** Extension → FileType mapping. */\nconst EXTENSION_MAP: ReadonlyMap<string, FileType> = new Map<string, FileType>([\n\t// Tree-sitter languages\n\t[\".ts\", \"typescript\"],\n\t[\".tsx\", \"tsx\"],\n\t[\".js\", \"javascript\"],\n\t[\".mjs\", \"javascript\"],\n\t[\".cjs\", \"javascript\"],\n\t[\".py\", \"python\"],\n\t[\".go\", \"go\"],\n\t[\".rs\", \"rust\"],\n\t[\".java\", \"java\"],\n\t[\".c\", \"c\"],\n\t[\".h\", \"c\"],\n\t[\".cpp\", \"cpp\"],\n\t[\".hpp\", \"cpp\"],\n\t[\".cc\", \"cpp\"],\n\t[\".cxx\", \"cpp\"],\n\t[\".hh\", \"cpp\"],\n\t[\".hxx\", \"cpp\"],\n\t// Text file types\n\t[\".md\", \"markdown\"],\n\t[\".mdx\", \"markdown\"],\n\t[\".yml\", \"yaml\"],\n\t[\".yaml\", \"yaml\"],\n\t[\".json\", \"json\"],\n\t[\".toml\", \"toml\"],\n\t[\".txt\", \"plaintext\"],\n\t[\".cfg\", \"plaintext\"],\n\t[\".ini\", \"plaintext\"],\n\t[\".env\", \"plaintext\"],\n\t[\".conf\", \"plaintext\"],\n]);\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Detect the {@link FileType} for a file path based on its extension.\n * Returns `null` for unrecognized extensions or files without an extension.\n */\nexport function detectFileType(filePath: string): FileType | null {\n\tconst ext = extname(filePath).toLowerCase();\n\tif (!ext) return null;\n\treturn EXTENSION_MAP.get(ext) ?? null;\n}\n\n/**\n * Scan a project directory and return all indexable files.\n *\n * Walks the tree rooted at {@link projectRoot}, respects `.gitignore` rules,\n * skips binary / oversized files, and optionally includes memory files from\n * a global memory directory.\n */\nexport async function scanProject(\n\tprojectRoot: string,\n\tglobalMemoryDir?: string,\n\tvisibleDirs?: string[],\n): Promise<ScannedFile[]> {\n\tconst results: ScannedFile[] = [];\n\n\t// Detect if projectRoot is the home directory — use shallow scan mode\n\t// to avoid recursing into the entire home dir (which would be catastrophic).\n\tconst isHomeDir = isHomeDirPath(projectRoot);\n\n\tif (isHomeDir) {\n\t\t// Shallow mode: only scan top-level files and ~/.dreb/memory/\n\t\tscanShallow(projectRoot, results);\n\t} else {\n\t\t// Normal mode: full recursive walk with .gitignore\n\t\tconst ig = ignore();\n\t\tloadGitignore(ig, projectRoot, projectRoot);\n\t\twalkDirectory(projectRoot, projectRoot, ig, results);\n\t}\n\n\t// Include tool-visible .dreb/ subdirs (bypasses gitignore).\n\t// In home dir mode, global memory is already handled separately below,\n\t// and we don't want to double-scan ~/.dreb/memory/.\n\tif (!isHomeDir) {\n\t\tfor (const dir of visibleDirs ?? []) {\n\t\t\tscanMemoryDir(dir, projectRoot, results);\n\t\t}\n\t}\n\n\t// Include global memory files if the directory exists\n\tif (globalMemoryDir && existsSync(globalMemoryDir)) {\n\t\tscanMemoryDir(globalMemoryDir, projectRoot, results);\n\t}\n\n\treturn results;\n}\n\n/** Check if a path is the user's home directory. */\nfunction isHomeDirPath(dir: string): boolean {\n\ttry {\n\t\tconst home = homedir();\n\t\t// Normalize trailing slashes for comparison\n\t\tconst normalizedDir = dir.replace(/[/\\\\]+$/, \"\");\n\t\tconst normalizedHome = home.replace(/[/\\\\]+$/, \"\");\n\t\treturn normalizedDir === normalizedHome;\n\t} catch {\n\t\t/* os.homedir() can throw in sandboxed/unusual environments */\n\t\treturn false;\n\t}\n}\n\n/**\n * Shallow scan mode for home directory: only index top-level files\n * (no directory recursion) to avoid scanning the entire home directory.\n * Memory files are handled separately via scanMemoryDir.\n */\nfunction scanShallow(dir: string, results: ScannedFile[]): void {\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(dir);\n\t} catch {\n\t\t/* Directory unreadable (EACCES, ENOENT) — abandon shallow scan */\n\t\treturn;\n\t}\n\n\tfor (const entry of entries) {\n\t\t// Skip dotfiles/dotdirs in home dir (except specific ones we want)\n\t\tif (entry.startsWith(\".\")) continue;\n\n\t\tconst fullPath = join(dir, entry);\n\n\t\tlet stats: Stats;\n\t\ttry {\n\t\t\tstats = statSync(fullPath);\n\t\t} catch {\n\t\t\t/* Broken symlink or race deletion — skip entry */\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Only index files, not directories (shallow mode)\n\t\tif (!stats.isFile()) continue;\n\t\tif (stats.size > MAX_FILE_SIZE) continue;\n\t\tif (stats.size === 0) continue;\n\n\t\tconst fileType = detectFileType(entry);\n\t\tif (!fileType) continue;\n\n\t\tresults.push({\n\t\t\tfilePath: entry,\n\t\t\tfileType,\n\t\t\tmtime: stats.mtimeMs,\n\t\t});\n\t}\n}\n\n// ============================================================================\n// Internal helpers\n// ============================================================================\n\ntype IgnoreMatcher = ReturnType<typeof ignore>;\n\n/** Convert an OS path to posix separators for ignore matching. */\nfunction toPosix(p: string): string {\n\treturn p.split(sep).join(\"/\");\n}\n\n/** Load .gitignore rules from a directory into the ignore matcher. */\nfunction loadGitignore(ig: IgnoreMatcher, dir: string, root: string): void {\n\tconst gitignorePath = join(dir, \".gitignore\");\n\tif (!existsSync(gitignorePath)) return;\n\n\ttry {\n\t\tconst content = readFileSync(gitignorePath, \"utf-8\");\n\t\tconst relDir = relative(root, dir);\n\t\tconst prefix = relDir ? `${toPosix(relDir)}/` : \"\";\n\n\t\tconst patterns = content\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((line) => prefixPattern(line, prefix))\n\t\t\t.filter((line): line is string => line !== null);\n\n\t\tif (patterns.length > 0) {\n\t\t\tig.add(patterns);\n\t\t}\n\t} catch {\n\t\t// Unreadable .gitignore — skip silently\n\t}\n}\n\n/**\n * Prefix a .gitignore pattern with a directory path so it applies\n * correctly when matching against root-relative paths.\n */\nfunction prefixPattern(line: string, prefix: string): string | null {\n\tconst trimmed = line.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.startsWith(\"#\") && !trimmed.startsWith(\"\\\\#\")) return null;\n\n\tlet pattern = line;\n\tlet negated = false;\n\n\tif (pattern.startsWith(\"!\")) {\n\t\tnegated = true;\n\t\tpattern = pattern.slice(1);\n\t} else if (pattern.startsWith(\"\\\\!\")) {\n\t\tpattern = pattern.slice(1);\n\t}\n\n\tconst prefixed = prefix ? `${prefix}${pattern}` : pattern;\n\treturn negated ? `!${prefixed}` : prefixed;\n}\n\n/**\n * Check if a directory component (relative to root) should be unconditionally skipped.\n * Handles both top-level names (\"node_modules\") and nested paths (\".dreb/index\").\n */\nfunction shouldSkipDir(relPath: string): boolean {\n\tconst posix = toPosix(relPath);\n\n\t// Check the directory name itself\n\tconst parts = posix.split(\"/\");\n\tconst name = parts[parts.length - 1];\n\tif (SKIP_DIRS.has(name)) return true;\n\n\t// Check multi-segment skip patterns (e.g. \".dreb/index\")\n\tfor (const skip of SKIP_DIRS) {\n\t\tif (skip.includes(\"/\") && (posix === skip || posix.endsWith(`/${skip}`))) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/** Recursively walk a directory, collecting indexable files. */\nfunction walkDirectory(dir: string, root: string, ig: IgnoreMatcher, results: ScannedFile[]): void {\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(dir);\n\t} catch {\n\t\t/* Directory unreadable (permission denied, etc.) — stop recursion into this branch */\n\t\treturn;\n\t}\n\n\tfor (const entry of entries) {\n\t\tconst fullPath = join(dir, entry);\n\t\tconst relPath = relative(root, fullPath);\n\t\tconst posixRel = toPosix(relPath);\n\n\t\tlet stats: Stats;\n\t\ttry {\n\t\t\tstats = statSync(fullPath);\n\t\t} catch {\n\t\t\t/* Broken symlink or race deletion — skip entry */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (stats.isDirectory()) {\n\t\t\t// Hard-coded skip list\n\t\t\tif (shouldSkipDir(relPath)) continue;\n\n\t\t\t// .gitignore check (directories need trailing slash)\n\t\t\tif (ig.ignores(`${posixRel}/`)) continue;\n\n\t\t\t// Load nested .gitignore before descending\n\t\t\tloadGitignore(ig, fullPath, root);\n\n\t\t\twalkDirectory(fullPath, root, ig, results);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!stats.isFile()) continue;\n\n\t\t// .gitignore check for files\n\t\tif (ig.ignores(posixRel)) continue;\n\n\t\t// Size gate\n\t\tif (stats.size > MAX_FILE_SIZE) continue;\n\t\tif (stats.size === 0) continue;\n\n\t\t// File type detection\n\t\tconst fileType = detectFileType(entry);\n\t\tif (!fileType) continue;\n\n\t\tresults.push({\n\t\t\tfilePath: posixRel,\n\t\t\tfileType,\n\t\t\tmtime: stats.mtimeMs,\n\t\t});\n\t}\n}\n\n/**\n * Scan a memory directory (project or global) for indexable files.\n *\n * Memory directories are always fully included — no .gitignore filtering —\n * because they live outside the normal project tree or in `.dreb/` which\n * is typically gitignored.\n *\n * Paths for global memory files are stored with a `~memory/` prefix\n * to distinguish them from project files.\n */\nfunction scanMemoryDir(memoryDir: string, projectRoot: string, results: ScannedFile[], baseMemoryDir?: string): void {\n\tlet entries: string[];\n\ttry {\n\t\tentries = readdirSync(memoryDir);\n\t} catch {\n\t\t/* Memory directory doesn't exist or is unreadable — skip */\n\t\treturn;\n\t}\n\n\tfor (const entry of entries) {\n\t\tconst fullPath = join(memoryDir, entry);\n\n\t\tlet stats: Stats;\n\t\ttry {\n\t\t\tstats = statSync(fullPath);\n\t\t} catch {\n\t\t\t/* Broken symlink or race deletion — skip entry */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (stats.isDirectory()) {\n\t\t\t// Recurse into subdirectories\n\t\t\tscanMemoryDir(fullPath, projectRoot, results, baseMemoryDir ?? memoryDir);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!stats.isFile()) continue;\n\t\tif (stats.size > MAX_FILE_SIZE) continue;\n\t\tif (stats.size === 0) continue;\n\n\t\tconst fileType = detectFileType(entry);\n\t\tif (!fileType) continue;\n\n\t\t// If the memory dir is inside the project root, use normal relative path.\n\t\t// Otherwise, use a ~memory/ prefix so paths remain unique and identifiable.\n\t\tconst rel = relative(projectRoot, fullPath);\n\t\tconst isOutsideProject = rel.startsWith(\"..\") || isAbsolute(rel);\n\t\tconst rootMemoryDir = baseMemoryDir ?? memoryDir;\n\t\tconst filePath = isOutsideProject ? `~memory/${relative(rootMemoryDir, fullPath)}` : rel;\n\n\t\tresults.push({\n\t\t\tfilePath: toPosix(filePath),\n\t\t\tfileType,\n\t\t\tmtime: stats.mtimeMs,\n\t\t});\n\t}\n}\n"]}
|