directory-indexer 0.2.2 → 0.3.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/dist/storage.js CHANGED
@@ -1,6 +1,25 @@
1
1
  import Database from "better-sqlite3";
2
2
  import { ensureDirectory } from "./utils.js";
3
3
  import { dirname } from "path";
4
+ function escapeLike(value) {
5
+ return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
6
+ }
7
+ function directoryLikeClause(column, dirPath) {
8
+ const escaped = escapeLike(dirPath);
9
+ return {
10
+ clause: `(${column} = ? OR ${column} LIKE ? ESCAPE '\\' OR ${column} LIKE ? ESCAPE '\\')`,
11
+ params: [dirPath, `${escaped}/%`, `${escaped}\\%`]
12
+ };
13
+ }
14
+ const openStorageInstances = /* @__PURE__ */ new Set();
15
+ function closeAllStorage() {
16
+ for (const instance of openStorageInstances) {
17
+ try {
18
+ instance.close();
19
+ } catch {
20
+ }
21
+ }
22
+ }
4
23
  class StorageError extends Error {
5
24
  constructor(message, cause) {
6
25
  super(message);
@@ -221,6 +240,7 @@ class SQLiteStorage {
221
240
  constructor(config) {
222
241
  this.config = config;
223
242
  this.db = this.initializeDatabase();
243
+ openStorageInstances.add(this);
224
244
  }
225
245
  db;
226
246
  initializeDatabase() {
@@ -250,6 +270,8 @@ class SQLiteStorage {
250
270
  CREATE INDEX IF NOT EXISTS idx_files_hash ON files(hash);
251
271
  CREATE INDEX IF NOT EXISTS idx_directories_path ON directories(path);
252
272
  `);
273
+ db.pragma("journal_mode = WAL");
274
+ db.pragma("busy_timeout = 5000");
253
275
  return db;
254
276
  } catch (error) {
255
277
  throw new StorageError(`Failed to initialize SQLite database`, error);
@@ -327,10 +349,15 @@ class SQLiteStorage {
327
349
  throw new StorageError(`Failed to delete file record`, error);
328
350
  }
329
351
  }
352
+ getDirectories() {
353
+ const rows = this.db.prepare("SELECT path FROM directories").all();
354
+ return rows.map((row) => row.path);
355
+ }
330
356
  async getFilesByDirectory(directoryPath) {
331
357
  try {
332
- const stmt = this.db.prepare("SELECT * FROM files WHERE path LIKE ?");
333
- const rows = stmt.all(`${directoryPath}%`);
358
+ const { clause, params } = directoryLikeClause("path", directoryPath);
359
+ const stmt = this.db.prepare(`SELECT * FROM files WHERE ${clause}`);
360
+ const rows = stmt.all(...params);
334
361
  return rows.map((row) => ({
335
362
  id: row.id,
336
363
  path: row.path,
@@ -345,7 +372,16 @@ class SQLiteStorage {
345
372
  throw new StorageError(`Failed to get files by directory`, error);
346
373
  }
347
374
  }
375
+ deleteDirectory(path) {
376
+ this.db.prepare("DELETE FROM directories WHERE path = ?").run(path);
377
+ }
378
+ deleteFilesByDirectory(directoryPath) {
379
+ const { clause, params } = directoryLikeClause("path", directoryPath);
380
+ const result = this.db.prepare(`DELETE FROM files WHERE ${clause}`).run(...params);
381
+ return result.changes;
382
+ }
348
383
  close() {
384
+ openStorageInstances.delete(this);
349
385
  this.db.close();
350
386
  }
351
387
  }
@@ -603,22 +639,23 @@ async function getIndexStatus() {
603
639
  }
604
640
  });
605
641
  const directoriesDetailStmt = sqlite.db.prepare(`
606
- SELECT
642
+ SELECT
607
643
  d.path,
608
644
  d.status,
609
645
  d.indexed_at,
610
- (SELECT COUNT(*) FROM files f WHERE f.path LIKE d.path || '%') as files_count,
611
- (SELECT COALESCE(SUM(json_array_length(f.chunks_json)), 0) FROM files f WHERE f.path LIKE d.path || '%' AND f.chunks_json IS NOT NULL) as chunks_count
646
+ (SELECT COUNT(*) FROM files f WHERE f.path = d.path OR f.path LIKE REPLACE(d.path, '\\', '\\\\') || '/%' ESCAPE '\\' OR f.path LIKE REPLACE(d.path, '\\', '\\\\') || '\\%' ESCAPE '\\') as files_count,
647
+ (SELECT COALESCE(SUM(json_array_length(f.chunks_json)), 0) FROM files f WHERE (f.path = d.path OR f.path LIKE REPLACE(d.path, '\\', '\\\\') || '/%' ESCAPE '\\' OR f.path LIKE REPLACE(d.path, '\\', '\\\\') || '\\%' ESCAPE '\\') AND f.chunks_json IS NOT NULL) as chunks_count
612
648
  FROM directories d
613
649
  ORDER BY d.indexed_at DESC
614
650
  `);
615
651
  const directoryDetails = directoriesDetailStmt.all();
616
652
  const directories = directoryDetails.map((row) => {
653
+ const { clause, params } = directoryLikeClause("path", row.path);
617
654
  const errorsByDirStmt = sqlite.db.prepare(`
618
- SELECT errors_json FROM files
619
- WHERE path LIKE ? || '%' AND errors_json IS NOT NULL
655
+ SELECT errors_json FROM files
656
+ WHERE ${clause} AND errors_json IS NOT NULL
620
657
  `);
621
- const dirErrors = errorsByDirStmt.all(row.path);
658
+ const dirErrors = errorsByDirStmt.all(...params);
622
659
  const dirErrorsList = [];
623
660
  dirErrors.forEach((errorRow) => {
624
661
  try {
@@ -677,6 +714,7 @@ export {
677
714
  StorageError,
678
715
  clearDatabase,
679
716
  clearVectorCollection,
717
+ closeAllStorage,
680
718
  getIndexStatus,
681
719
  getResetPreview,
682
720
  initDatabase,
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sources":["../src/storage.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { Config } from './config.js';\nimport { FileInfo, ChunkInfo, ensureDirectory } from './utils.js';\nimport { dirname } from 'path';\n\nexport interface DirectoryRecord {\n id: number;\n path: string;\n status: 'pending' | 'indexing' | 'completed' | 'failed';\n indexedAt: Date;\n}\n\nexport interface FileRecord {\n id: number;\n path: string;\n size: number;\n modifiedTime: Date;\n hash: string;\n parentDirs: string[];\n chunks: ChunkInfo[];\n errors?: string[];\n}\n\nexport interface QdrantPoint {\n id: string | number;\n vector: number[];\n payload: {\n filePath: string;\n chunkId: string;\n fileHash: string;\n content: string;\n parentDirectories: string[];\n };\n score?: number;\n}\n\nexport class StorageError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'StorageError';\n }\n}\n\nexport class QdrantClient {\n constructor(private config: Config) {}\n\n async healthCheck(): Promise<boolean> {\n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/healthz`);\n return response.ok;\n } catch {\n return false;\n }\n }\n\n async createCollection(): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const checkResponse = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`);\n if (checkResponse.ok) {\n return;\n }\n\n const createResponse = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n vectors: {\n size: 768,\n distance: 'Cosine'\n }\n })\n });\n\n if (!createResponse.ok) {\n throw new Error(`Failed to create collection: ${createResponse.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to create Qdrant collection`, error as Error);\n }\n }\n\n async upsertPoints(points: QdrantPoint[]): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ points })\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to upsert points: ${response.status} ${response.statusText} - ${errorText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to upsert points to Qdrant`, error as Error);\n }\n }\n\n async searchPoints(vector: number[], limit: number = 10, filter?: Record<string, unknown>): Promise<QdrantPoint[]> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const searchBody: Record<string, unknown> = {\n vector,\n limit,\n with_payload: true\n };\n \n if (filter) {\n searchBody.filter = filter;\n }\n \n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/search`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(searchBody)\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n throw new Error(`Failed to search points: ${response.statusText} - ${errorBody}`);\n }\n\n const data = await response.json();\n return data.result.map((item: { id: string | number; vector: number[]; payload: Record<string, unknown>; score: number }) => ({\n id: item.id,\n vector: item.vector,\n payload: item.payload,\n score: item.score\n }));\n } catch (error) {\n throw new StorageError(`Failed to search points in Qdrant`, error as Error);\n }\n }\n\n async deletePoints(ids: (string | number)[]): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/delete`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ points: ids })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete points: ${response.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete points from Qdrant`, error as Error);\n }\n }\n\n async deletePointsByFilePath(filePath: string): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/delete`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n filter: {\n must: [\n {\n key: 'filePath',\n match: { value: filePath }\n }\n ]\n }\n })\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to delete points by file path: ${response.status} ${response.statusText} - ${errorText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete points by file path from Qdrant`, error as Error);\n }\n }\n\n async countPoints(filter?: Record<string, unknown>): Promise<number> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const countBody: Record<string, unknown> = { exact: true };\n if (filter) {\n countBody.filter = filter;\n }\n\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/count`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(countBody)\n });\n\n if (!response.ok) {\n throw new Error(`Failed to count points: ${response.statusText}`);\n }\n\n const data = await response.json();\n return data.result.count;\n } catch (error) {\n throw new StorageError(`Failed to count points in Qdrant`, error as Error);\n }\n }\n\n async scrollPoints(filter?: Record<string, unknown>, limit: number = 1000): Promise<QdrantPoint[]> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const scrollBody: Record<string, unknown> = {\n limit,\n with_payload: true,\n with_vector: false\n };\n if (filter) {\n scrollBody.filter = filter;\n }\n\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/scroll`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(scrollBody)\n });\n\n if (!response.ok) {\n throw new Error(`Failed to scroll points: ${response.statusText}`);\n }\n\n const data = await response.json();\n return data.result.points.map((item: { id: string | number; payload: Record<string, unknown> }) => ({\n id: item.id,\n vector: [],\n payload: item.payload,\n score: 0\n }));\n } catch (error) {\n throw new StorageError(`Failed to scroll points in Qdrant`, error as Error);\n }\n }\n\n async getCollectionInfo(): Promise<{ vectors_count?: number } | null> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`);\n \n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get collection info: ${response.statusText}`);\n }\n\n const data = await response.json();\n return {\n vectors_count: data.result?.points_count || data.result?.vectors_count || 0\n };\n } catch (error) {\n throw new StorageError(`Failed to get collection info from Qdrant`, error as Error);\n }\n }\n\n async deleteCollection(): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`, {\n method: 'DELETE',\n headers: this.config.storage.qdrantApiKey ? {\n 'api-key': this.config.storage.qdrantApiKey\n } : {}\n });\n\n if (!response.ok && response.status !== 404) {\n throw new Error(`Failed to delete collection: ${response.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete collection from Qdrant`, error as Error);\n }\n }\n}\n\nexport class SQLiteStorage {\n public db: Database.Database;\n\n constructor(private config: Config) {\n this.db = this.initializeDatabase();\n }\n\n private initializeDatabase(): Database.Database {\n try {\n ensureDirectory(dirname(this.config.storage.sqlitePath));\n \n const db = new Database(this.config.storage.sqlitePath);\n \n db.exec(`\n CREATE TABLE IF NOT EXISTS directories (\n id INTEGER PRIMARY KEY,\n path TEXT UNIQUE NOT NULL,\n status TEXT DEFAULT 'pending',\n indexed_at INTEGER DEFAULT 0\n );\n\n CREATE TABLE IF NOT EXISTS files (\n id INTEGER PRIMARY KEY,\n path TEXT UNIQUE NOT NULL,\n size INTEGER NOT NULL,\n modified_time INTEGER NOT NULL,\n hash TEXT NOT NULL,\n parent_dirs TEXT NOT NULL,\n chunks_json TEXT,\n errors_json TEXT\n );\n\n CREATE INDEX IF NOT EXISTS idx_files_path ON files(path);\n CREATE INDEX IF NOT EXISTS idx_files_hash ON files(hash);\n CREATE INDEX IF NOT EXISTS idx_directories_path ON directories(path);\n `);\n\n return db;\n } catch (error) {\n throw new StorageError(`Failed to initialize SQLite database`, error as Error);\n }\n }\n\n async getDirectory(path: string): Promise<DirectoryRecord | null> {\n try {\n const stmt = this.db.prepare('SELECT * FROM directories WHERE path = ?');\n const row = stmt.get(path) as { id: number; path: string; status: 'pending' | 'indexing' | 'completed' | 'failed'; indexed_at: number } | undefined;\n \n if (!row) return null;\n \n return {\n id: row.id,\n path: row.path,\n status: row.status,\n indexedAt: new Date(row.indexed_at)\n };\n } catch (error) {\n throw new StorageError(`Failed to get directory record`, error as Error);\n }\n }\n\n async upsertDirectory(path: string, status: DirectoryRecord['status']): Promise<void> {\n try {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO directories (path, status, indexed_at)\n VALUES (?, ?, ?)\n `);\n \n stmt.run(path, status, Date.now());\n } catch (error) {\n throw new StorageError(`Failed to upsert directory record`, error as Error);\n }\n }\n\n async getFile(path: string): Promise<FileRecord | null> {\n try {\n const stmt = this.db.prepare('SELECT * FROM files WHERE path = ?');\n const row = stmt.get(path) as { id: number; path: string; size: number; modified_time: number; hash: string; parent_dirs: string; chunks_json: string | null; errors_json: string | null } | undefined;\n \n if (!row) return null;\n \n return {\n id: row.id,\n path: row.path,\n size: row.size,\n modifiedTime: new Date(row.modified_time * 1000),\n hash: row.hash,\n parentDirs: JSON.parse(row.parent_dirs),\n chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],\n errors: row.errors_json ? JSON.parse(row.errors_json) : undefined\n };\n } catch (error) {\n throw new StorageError(`Failed to get file record`, error as Error);\n }\n }\n\n async upsertFile(fileInfo: FileInfo, chunks: ChunkInfo[] = [], errors: string[] = []): Promise<void> {\n try {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO files (path, size, modified_time, hash, parent_dirs, chunks_json, errors_json)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n \n stmt.run(\n fileInfo.path,\n fileInfo.size,\n fileInfo.modifiedTime.getTime(),\n fileInfo.hash,\n JSON.stringify(fileInfo.parentDirs),\n chunks.length > 0 ? JSON.stringify(chunks) : null,\n errors.length > 0 ? JSON.stringify(errors) : null\n );\n } catch (error) {\n throw new StorageError(`Failed to upsert file record`, error as Error);\n }\n }\n\n async deleteFile(path: string): Promise<void> {\n try {\n const stmt = this.db.prepare('DELETE FROM files WHERE path = ?');\n stmt.run(path);\n } catch (error) {\n throw new StorageError(`Failed to delete file record`, error as Error);\n }\n }\n\n async getFilesByDirectory(directoryPath: string): Promise<FileRecord[]> {\n try {\n const stmt = this.db.prepare('SELECT * FROM files WHERE path LIKE ?');\n const rows = stmt.all(`${directoryPath}%`) as { id: number; path: string; size: number; modified_time: number; hash: string; parent_dirs: string; chunks_json: string | null; errors_json: string | null }[];\n \n return rows.map(row => ({\n id: row.id,\n path: row.path,\n size: row.size,\n modifiedTime: new Date(row.modified_time * 1000),\n hash: row.hash,\n parentDirs: JSON.parse(row.parent_dirs),\n chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],\n errors: row.errors_json ? JSON.parse(row.errors_json) : undefined\n }));\n } catch (error) {\n throw new StorageError(`Failed to get files by directory`, error as Error);\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n\nexport async function initializeStorage(config: Config): Promise<{ sqlite: SQLiteStorage; qdrant: QdrantClient }> {\n const sqlite = new SQLiteStorage(config);\n const qdrant = new QdrantClient(config);\n \n await qdrant.createCollection();\n \n return { sqlite, qdrant };\n}\n\nexport async function initDatabase(dbPath: string): Promise<Database.Database> {\n return new Database(dbPath);\n}\n\nexport interface ResetStats {\n sqliteExists: boolean;\n sqliteSize?: string;\n qdrantCollectionExists: boolean;\n qdrantVectorCount?: number;\n}\n\nexport interface ResetResult {\n sqliteDeleted: boolean;\n qdrantDeleted: boolean;\n warnings: string[];\n}\n\nexport async function getResetPreview(config: Config): Promise<ResetStats> {\n const stats: ResetStats = {\n sqliteExists: false,\n qdrantCollectionExists: false\n };\n\n if (await import('fs').then(fs => fs.existsSync(config.storage.sqlitePath))) {\n stats.sqliteExists = true;\n try {\n const fileStats = await import('fs').then(fs => fs.statSync(config.storage.sqlitePath));\n const sizeInMB = (fileStats.size / (1024 * 1024)).toFixed(1);\n stats.sqliteSize = `${sizeInMB} MB`;\n } catch {\n stats.sqliteSize = 'unknown size';\n }\n }\n\n try {\n const qdrant = new QdrantClient(config);\n const isHealthy = await qdrant.healthCheck();\n \n if (isHealthy) {\n const collectionInfo = await qdrant.getCollectionInfo();\n if (collectionInfo) {\n stats.qdrantCollectionExists = true;\n stats.qdrantVectorCount = collectionInfo.vectors_count || 0;\n }\n }\n } catch {\n // Qdrant unavailable\n }\n\n return stats;\n}\n\nexport async function clearDatabase(config: Config): Promise<boolean> {\n try {\n if (!await import('fs').then(fs => fs.existsSync(config.storage.sqlitePath))) {\n return true; // Already clean\n }\n \n await import('fs/promises').then(fs => fs.unlink(config.storage.sqlitePath));\n return true;\n } catch (error) {\n throw new StorageError(`Failed to delete SQLite database: ${error instanceof Error ? error.message : 'Unknown error'}`, error as Error);\n }\n}\n\nexport async function clearVectorCollection(config: Config): Promise<boolean> {\n try {\n const qdrant = new QdrantClient(config);\n const isHealthy = await qdrant.healthCheck();\n \n if (!isHealthy) {\n throw new StorageError(`Qdrant unavailable at ${config.storage.qdrantEndpoint}`);\n }\n \n const collectionInfo = await qdrant.getCollectionInfo();\n if (collectionInfo) {\n await qdrant.deleteCollection();\n }\n \n return true;\n } catch (error) {\n throw new StorageError(`Failed to reset Qdrant collection: ${error instanceof Error ? error.message : 'Unknown error'}`, error as Error);\n }\n}\n\nexport interface DirectoryStatus {\n path: string;\n status: string;\n filesCount: number;\n chunksCount: number;\n lastIndexed: string | null;\n errors: string[];\n}\n\nexport interface WorkspaceStatus {\n name: string;\n paths: string[];\n isValid: boolean;\n filesCount: number;\n chunksCount: number;\n health: {\n status: 'healthy' | 'warning' | 'error';\n issues: string[];\n recommendations: string[];\n };\n}\n\nexport interface IndexStatus {\n directoriesIndexed: number;\n filesIndexed: number;\n chunksIndexed: number;\n databaseSize: string;\n lastIndexed: string | null;\n errors: string[];\n directories: DirectoryStatus[];\n workspaces: WorkspaceStatus[];\n workspaceHealth: {\n healthy: number;\n warnings: number;\n errors: number;\n criticalIssues: string[];\n recommendations: string[];\n };\n qdrantConsistency: {\n isConsistent: boolean;\n issues: string[];\n };\n}\n\nasync function calculateWorkspaceStatistics(sqlite: SQLiteStorage, config: Config): Promise<WorkspaceStatus[]> {\n const { getAvailableWorkspaces, getWorkspacePaths } = await import('./config.js');\n const workspaces: WorkspaceStatus[] = [];\n \n // Get all indexed directories for comparison\n const directoriesStmt = sqlite.db.prepare('SELECT path, status FROM directories');\n const indexedDirectories = directoriesStmt.all() as { path: string; status: string }[];\n const indexedDirectoryPaths = indexedDirectories.map(d => d.path);\n \n // Get Qdrant client for efficient workspace filtering\n const qdrant = new QdrantClient(config);\n \n for (const workspaceName of getAvailableWorkspaces(config)) {\n const workspacePaths = getWorkspacePaths(config, workspaceName);\n const workspaceConfig = config.workspaces[workspaceName];\n \n let filesCount = 0;\n let chunksCount = 0;\n \n if (workspacePaths.length > 0) {\n try {\n // Build workspace filter using same logic as search\n const workspaceFilter = {\n must: [\n {\n key: \"parentDirectories\",\n match: { any: workspacePaths }\n }\n ]\n };\n \n // Get chunk count efficiently using Qdrant\n chunksCount = await qdrant.countPoints(workspaceFilter);\n \n // Get unique file paths using Qdrant scroll\n const points = await qdrant.scrollPoints(workspaceFilter, 10000);\n const uniqueFilePaths = new Set(points.map(p => p.payload.filePath));\n filesCount = uniqueFilePaths.size;\n \n } catch {\n // Fallback to 0 if Qdrant is unavailable\n filesCount = 0;\n chunksCount = 0;\n }\n }\n \n // Check workspace health\n const health = analyzeWorkspaceHealth(workspacePaths, workspaceConfig.isValid, filesCount, indexedDirectoryPaths);\n \n workspaces.push({\n name: workspaceName,\n paths: workspacePaths,\n isValid: workspaceConfig.isValid,\n filesCount,\n chunksCount,\n health\n });\n }\n \n return workspaces;\n}\n\nfunction analyzeWorkspaceHealth(\n workspacePaths: string[], \n isValid: boolean, \n filesCount: number, \n indexedDirectoryPaths: string[]\n): { status: 'healthy' | 'warning' | 'error'; issues: string[]; recommendations: string[] } {\n const issues: string[] = [];\n const recommendations: string[] = [];\n \n // Check if workspace paths exist and are valid\n if (!isValid) {\n issues.push('One or more workspace directories do not exist on the filesystem');\n recommendations.push('Verify workspace directory paths exist and are accessible');\n }\n \n // Check if workspace paths are indexed\n const unindexedPaths: string[] = [];\n const partiallyIndexedPaths: string[] = [];\n \n for (const workspacePath of workspacePaths) {\n // Check if this exact path is indexed\n const exactMatch = indexedDirectoryPaths.includes(workspacePath);\n \n if (exactMatch) {\n // Perfect match - workspace directory is directly indexed\n continue;\n }\n \n // Check if workspace path is covered by a parent directory that is indexed\n const isChildOfIndexed = indexedDirectoryPaths.some(indexedPath => {\n // Workspace path must start with indexed path and be a subdirectory\n return workspacePath.startsWith(indexedPath + '/') || workspacePath.startsWith(indexedPath + '\\\\');\n });\n \n if (isChildOfIndexed) {\n partiallyIndexedPaths.push(workspacePath);\n } else {\n unindexedPaths.push(workspacePath);\n }\n }\n \n // Report unindexed paths\n if (unindexedPaths.length > 0) {\n issues.push(`Workspace directories not indexed: ${unindexedPaths.join(', ')}`);\n recommendations.push(`Run indexing on these directories: ${unindexedPaths.join(', ')}`);\n }\n \n // Report partially indexed paths (where parent directory is indexed)\n if (partiallyIndexedPaths.length > 0) {\n issues.push(`Workspace directories indexed as part of parent directory: ${partiallyIndexedPaths.join(', ')}`);\n recommendations.push('Consider indexing workspace directories directly for better organization');\n }\n \n // Check if workspace is empty (no files found)\n if (isValid && filesCount === 0 && unindexedPaths.length === 0) {\n issues.push('Workspace contains no indexed files');\n recommendations.push('Verify the workspace directory contains files and re-index if necessary');\n }\n \n // Determine overall health status\n let status: 'healthy' | 'warning' | 'error';\n if (!isValid || unindexedPaths.length > 0) {\n status = 'error';\n } else if (issues.length > 0) {\n status = 'warning';\n } else {\n status = 'healthy';\n }\n \n return { status, issues, recommendations };\n}\n\nfunction calculateWorkspaceHealthSummary(workspaces: WorkspaceStatus[]): {\n healthy: number;\n warnings: number; \n errors: number;\n criticalIssues: string[];\n recommendations: string[];\n} {\n let healthy = 0;\n let warnings = 0;\n let errors = 0;\n const criticalIssues: string[] = [];\n const recommendations: string[] = [];\n \n for (const workspace of workspaces) {\n switch (workspace.health.status) {\n case 'healthy':\n healthy++;\n break;\n case 'warning':\n warnings++;\n break;\n case 'error':\n errors++;\n criticalIssues.push(`${workspace.name}: ${workspace.health.issues.join(', ')}`);\n break;\n }\n \n // Collect unique recommendations\n for (const rec of workspace.health.recommendations) {\n if (!recommendations.includes(rec)) {\n recommendations.push(rec);\n }\n }\n }\n \n return {\n healthy,\n warnings,\n errors,\n criticalIssues,\n recommendations\n };\n}\n\nasync function checkQdrantConsistency(sqlite: SQLiteStorage, config: Config): Promise<{ isConsistent: boolean; issues: string[] }> {\n const issues: string[] = [];\n \n try {\n const qdrant = new QdrantClient(config);\n const isHealthy = await qdrant.healthCheck();\n \n if (!isHealthy) {\n issues.push('Qdrant vector database is not running or accessible');\n return { isConsistent: false, issues };\n }\n \n const filesWithChunksStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM files WHERE chunks_json IS NOT NULL');\n const filesWithChunks = filesWithChunksStmt.get() as { count: number };\n \n const totalChunksStmt = sqlite.db.prepare('SELECT SUM(json_array_length(chunks_json)) as count FROM files WHERE chunks_json IS NOT NULL');\n const totalChunks = totalChunksStmt.get() as { count: number | null };\n \n if (filesWithChunks.count > 0 && (totalChunks.count || 0) === 0) {\n issues.push('Files exist but no chunks found - possible data corruption');\n }\n \n const collectionName = config.storage.qdrantCollection;\n try {\n const response = await fetch(`${config.storage.qdrantEndpoint}/collections/${collectionName}`);\n if (!response.ok) {\n issues.push(`Vector collection '${collectionName}' not found (normal during first-time setup)`);\n return { isConsistent: false, issues };\n }\n \n const collectionInfo = await response.json();\n const qdrantPointCount = collectionInfo.result?.points_count || 0;\n const sqliteChunkCount = totalChunks.count || 0;\n \n if (Math.abs(qdrantPointCount - sqliteChunkCount) > 0) {\n if (qdrantPointCount > sqliteChunkCount) {\n issues.push(`Extra vectors in database: ${qdrantPointCount} vectors vs ${sqliteChunkCount} indexed chunks (normal during cleanup)`);\n } else {\n issues.push(`Missing vectors: ${sqliteChunkCount} indexed chunks vs ${qdrantPointCount} vectors (normal during indexing)`);\n }\n }\n } catch (error) {\n issues.push(`Cannot verify vector database status: ${error}`);\n }\n \n } catch (error) {\n issues.push(`Database status check failed: ${error}`);\n }\n \n return {\n isConsistent: issues.length === 0,\n issues\n };\n}\n\nexport async function getIndexStatus(): Promise<IndexStatus> {\n const config = await import('./config.js').then(m => m.loadConfig());\n const sqlite = new SQLiteStorage(config);\n \n try {\n const directoriesStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM directories WHERE status = ?');\n const directoriesCount = directoriesStmt.get('completed') as { count: number };\n \n const filesStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM files');\n const filesCount = filesStmt.get() as { count: number };\n \n const chunksStmt = sqlite.db.prepare('SELECT SUM(json_array_length(chunks_json)) as count FROM files WHERE chunks_json IS NOT NULL');\n const chunksCount = chunksStmt.get() as { count: number | null };\n \n const lastIndexedStmt = sqlite.db.prepare('SELECT MAX(indexed_at) as last_indexed FROM directories WHERE indexed_at > 0');\n const lastIndexedResult = lastIndexedStmt.get() as { last_indexed: number | null };\n \n const errorsStmt = sqlite.db.prepare('SELECT errors_json FROM files WHERE errors_json IS NOT NULL');\n const errorRows = errorsStmt.all() as { errors_json: string }[];\n \n const allErrors: string[] = [];\n errorRows.forEach(row => {\n try {\n const errors = JSON.parse(row.errors_json);\n allErrors.push(...errors);\n } catch {\n allErrors.push('Failed to parse error JSON');\n }\n });\n \n const directoriesDetailStmt = sqlite.db.prepare(`\n SELECT \n d.path,\n d.status,\n d.indexed_at,\n (SELECT COUNT(*) FROM files f WHERE f.path LIKE d.path || '%') as files_count,\n (SELECT COALESCE(SUM(json_array_length(f.chunks_json)), 0) FROM files f WHERE f.path LIKE d.path || '%' AND f.chunks_json IS NOT NULL) as chunks_count\n FROM directories d\n ORDER BY d.indexed_at DESC\n `);\n const directoryDetails = directoriesDetailStmt.all() as { id: number; path: string; status: 'pending' | 'indexing' | 'completed' | 'failed'; indexed_at: number; files_count: number; chunks_count: number }[];\n \n const directories: DirectoryStatus[] = directoryDetails.map(row => {\n const errorsByDirStmt = sqlite.db.prepare(`\n SELECT errors_json FROM files \n WHERE path LIKE ? || '%' AND errors_json IS NOT NULL\n `);\n const dirErrors = errorsByDirStmt.all(row.path) as { errors_json: string }[];\n \n const dirErrorsList: string[] = [];\n dirErrors.forEach(errorRow => {\n try {\n const errors = JSON.parse(errorRow.errors_json);\n dirErrorsList.push(...errors);\n } catch {\n dirErrorsList.push('Failed to parse error JSON');\n }\n });\n \n return {\n path: row.path,\n status: row.status,\n filesCount: row.files_count,\n chunksCount: row.chunks_count,\n lastIndexed: row.indexed_at && row.indexed_at > 0 ? new Date(row.indexed_at).toISOString() : null,\n errors: dirErrorsList\n };\n });\n \n const qdrantConsistency = await checkQdrantConsistency(sqlite, config);\n const workspaces = await calculateWorkspaceStatistics(sqlite, config);\n \n // Calculate workspace health summary\n const workspaceHealth = calculateWorkspaceHealthSummary(workspaces);\n \n const fs = await import('fs');\n let databaseSize = '0 KB';\n try {\n const stats = fs.statSync(config.storage.sqlitePath);\n const sizeInBytes = stats.size;\n if (sizeInBytes > 1024 * 1024) {\n databaseSize = `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`;\n } else if (sizeInBytes > 1024) {\n databaseSize = `${(sizeInBytes / 1024).toFixed(2)} KB`;\n } else {\n databaseSize = `${sizeInBytes} bytes`;\n }\n } catch {\n databaseSize = 'Unknown';\n }\n \n return {\n directoriesIndexed: directoriesCount.count,\n filesIndexed: filesCount.count,\n chunksIndexed: chunksCount.count || 0,\n databaseSize,\n lastIndexed: lastIndexedResult.last_indexed ? new Date(lastIndexedResult.last_indexed).toISOString() : null,\n errors: allErrors,\n directories,\n workspaces,\n workspaceHealth,\n qdrantConsistency\n };\n } finally {\n sqlite.close();\n }\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storage.js","sources":["../src/storage.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { Config } from './config.js';\nimport { FileInfo, ChunkInfo, ensureDirectory } from './utils.js';\nimport { dirname } from 'path';\n\n/**\n * Escape special characters in a string for use in a SQL LIKE pattern.\n * Escapes %, _, and \\ using \\ as the escape character.\n */\nfunction escapeLike(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/%/g, '\\\\%').replace(/_/g, '\\\\_');\n}\n\n/**\n * Build a SQL WHERE clause that matches files within a directory,\n * handling both `/` and `\\` as path separators so queries work\n * regardless of how paths were stored.\n *\n * Returns [clause, ...params] where clause uses `?` placeholders.\n */\nfunction directoryLikeClause(column: string, dirPath: string): { clause: string; params: string[] } {\n const escaped = escapeLike(dirPath);\n return {\n clause: `(${column} = ? OR ${column} LIKE ? ESCAPE '\\\\' OR ${column} LIKE ? ESCAPE '\\\\')`,\n params: [dirPath, `${escaped}/%`, `${escaped}\\\\%`]\n };\n}\n\n// Registry of all open SQLiteStorage instances for graceful shutdown\nconst openStorageInstances = new Set<SQLiteStorage>();\n\n/**\n * Close all open SQLiteStorage instances. Called during graceful shutdown.\n */\nexport function closeAllStorage(): void {\n for (const instance of openStorageInstances) {\n try {\n instance.close();\n } catch {\n // Ignore errors during shutdown cleanup\n }\n }\n}\n\nexport interface DirectoryRecord {\n id: number;\n path: string;\n status: 'pending' | 'indexing' | 'completed' | 'failed';\n indexedAt: Date;\n}\n\nexport interface FileRecord {\n id: number;\n path: string;\n size: number;\n modifiedTime: Date;\n hash: string;\n parentDirs: string[];\n chunks: ChunkInfo[];\n errors?: string[];\n}\n\nexport interface QdrantPoint {\n id: string | number;\n vector: number[];\n payload: {\n filePath: string;\n chunkId: string;\n fileHash: string;\n content: string;\n parentDirectories: string[];\n };\n score?: number;\n}\n\nexport class StorageError extends Error {\n constructor(message: string, public override cause?: Error) {\n super(message);\n this.name = 'StorageError';\n }\n}\n\nexport class QdrantClient {\n constructor(private config: Config) {}\n\n async healthCheck(): Promise<boolean> {\n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/healthz`);\n return response.ok;\n } catch {\n return false;\n }\n }\n\n async createCollection(): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const checkResponse = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`);\n if (checkResponse.ok) {\n return;\n }\n\n const createResponse = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n vectors: {\n size: 768,\n distance: 'Cosine'\n }\n })\n });\n\n if (!createResponse.ok) {\n throw new Error(`Failed to create collection: ${createResponse.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to create Qdrant collection`, error as Error);\n }\n }\n\n async upsertPoints(points: QdrantPoint[]): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ points })\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to upsert points: ${response.status} ${response.statusText} - ${errorText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to upsert points to Qdrant`, error as Error);\n }\n }\n\n async searchPoints(vector: number[], limit: number = 10, filter?: Record<string, unknown>): Promise<QdrantPoint[]> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const searchBody: Record<string, unknown> = {\n vector,\n limit,\n with_payload: true\n };\n \n if (filter) {\n searchBody.filter = filter;\n }\n \n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/search`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(searchBody)\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n throw new Error(`Failed to search points: ${response.statusText} - ${errorBody}`);\n }\n\n const data = await response.json();\n return data.result.map((item: { id: string | number; vector: number[]; payload: Record<string, unknown>; score: number }) => ({\n id: item.id,\n vector: item.vector,\n payload: item.payload,\n score: item.score\n }));\n } catch (error) {\n throw new StorageError(`Failed to search points in Qdrant`, error as Error);\n }\n }\n\n async deletePoints(ids: (string | number)[]): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/delete`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ points: ids })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to delete points: ${response.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete points from Qdrant`, error as Error);\n }\n }\n\n async deletePointsByFilePath(filePath: string): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/delete`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n filter: {\n must: [\n {\n key: 'filePath',\n match: { value: filePath }\n }\n ]\n }\n })\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to delete points by file path: ${response.status} ${response.statusText} - ${errorText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete points by file path from Qdrant`, error as Error);\n }\n }\n\n async countPoints(filter?: Record<string, unknown>): Promise<number> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const countBody: Record<string, unknown> = { exact: true };\n if (filter) {\n countBody.filter = filter;\n }\n\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/count`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(countBody)\n });\n\n if (!response.ok) {\n throw new Error(`Failed to count points: ${response.statusText}`);\n }\n\n const data = await response.json();\n return data.result.count;\n } catch (error) {\n throw new StorageError(`Failed to count points in Qdrant`, error as Error);\n }\n }\n\n async scrollPoints(filter?: Record<string, unknown>, limit: number = 1000): Promise<QdrantPoint[]> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const scrollBody: Record<string, unknown> = {\n limit,\n with_payload: true,\n with_vector: false\n };\n if (filter) {\n scrollBody.filter = filter;\n }\n\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}/points/scroll`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(scrollBody)\n });\n\n if (!response.ok) {\n throw new Error(`Failed to scroll points: ${response.statusText}`);\n }\n\n const data = await response.json();\n return data.result.points.map((item: { id: string | number; payload: Record<string, unknown> }) => ({\n id: item.id,\n vector: [],\n payload: item.payload,\n score: 0\n }));\n } catch (error) {\n throw new StorageError(`Failed to scroll points in Qdrant`, error as Error);\n }\n }\n\n async getCollectionInfo(): Promise<{ vectors_count?: number } | null> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`);\n \n if (!response.ok) {\n if (response.status === 404) {\n return null;\n }\n throw new Error(`Failed to get collection info: ${response.statusText}`);\n }\n\n const data = await response.json();\n return {\n vectors_count: data.result?.points_count || data.result?.vectors_count || 0\n };\n } catch (error) {\n throw new StorageError(`Failed to get collection info from Qdrant`, error as Error);\n }\n }\n\n async deleteCollection(): Promise<void> {\n const collectionName = this.config.storage.qdrantCollection;\n \n try {\n const response = await fetch(`${this.config.storage.qdrantEndpoint}/collections/${collectionName}`, {\n method: 'DELETE',\n headers: this.config.storage.qdrantApiKey ? {\n 'api-key': this.config.storage.qdrantApiKey\n } : {}\n });\n\n if (!response.ok && response.status !== 404) {\n throw new Error(`Failed to delete collection: ${response.statusText}`);\n }\n } catch (error) {\n throw new StorageError(`Failed to delete collection from Qdrant`, error as Error);\n }\n }\n}\n\nexport class SQLiteStorage {\n public db: Database.Database;\n\n constructor(private config: Config) {\n this.db = this.initializeDatabase();\n openStorageInstances.add(this);\n }\n\n private initializeDatabase(): Database.Database {\n try {\n ensureDirectory(dirname(this.config.storage.sqlitePath));\n \n const db = new Database(this.config.storage.sqlitePath);\n \n db.exec(`\n CREATE TABLE IF NOT EXISTS directories (\n id INTEGER PRIMARY KEY,\n path TEXT UNIQUE NOT NULL,\n status TEXT DEFAULT 'pending',\n indexed_at INTEGER DEFAULT 0\n );\n\n CREATE TABLE IF NOT EXISTS files (\n id INTEGER PRIMARY KEY,\n path TEXT UNIQUE NOT NULL,\n size INTEGER NOT NULL,\n modified_time INTEGER NOT NULL,\n hash TEXT NOT NULL,\n parent_dirs TEXT NOT NULL,\n chunks_json TEXT,\n errors_json TEXT\n );\n\n CREATE INDEX IF NOT EXISTS idx_files_path ON files(path);\n CREATE INDEX IF NOT EXISTS idx_files_hash ON files(hash);\n CREATE INDEX IF NOT EXISTS idx_directories_path ON directories(path);\n `);\n\n db.pragma('journal_mode = WAL');\n db.pragma('busy_timeout = 5000');\n\n return db;\n } catch (error) {\n throw new StorageError(`Failed to initialize SQLite database`, error as Error);\n }\n }\n\n async getDirectory(path: string): Promise<DirectoryRecord | null> {\n try {\n const stmt = this.db.prepare('SELECT * FROM directories WHERE path = ?');\n const row = stmt.get(path) as { id: number; path: string; status: 'pending' | 'indexing' | 'completed' | 'failed'; indexed_at: number } | undefined;\n \n if (!row) return null;\n \n return {\n id: row.id,\n path: row.path,\n status: row.status,\n indexedAt: new Date(row.indexed_at)\n };\n } catch (error) {\n throw new StorageError(`Failed to get directory record`, error as Error);\n }\n }\n\n async upsertDirectory(path: string, status: DirectoryRecord['status']): Promise<void> {\n try {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO directories (path, status, indexed_at)\n VALUES (?, ?, ?)\n `);\n \n stmt.run(path, status, Date.now());\n } catch (error) {\n throw new StorageError(`Failed to upsert directory record`, error as Error);\n }\n }\n\n async getFile(path: string): Promise<FileRecord | null> {\n try {\n const stmt = this.db.prepare('SELECT * FROM files WHERE path = ?');\n const row = stmt.get(path) as { id: number; path: string; size: number; modified_time: number; hash: string; parent_dirs: string; chunks_json: string | null; errors_json: string | null } | undefined;\n \n if (!row) return null;\n \n return {\n id: row.id,\n path: row.path,\n size: row.size,\n modifiedTime: new Date(row.modified_time * 1000),\n hash: row.hash,\n parentDirs: JSON.parse(row.parent_dirs),\n chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],\n errors: row.errors_json ? JSON.parse(row.errors_json) : undefined\n };\n } catch (error) {\n throw new StorageError(`Failed to get file record`, error as Error);\n }\n }\n\n async upsertFile(fileInfo: FileInfo, chunks: ChunkInfo[] = [], errors: string[] = []): Promise<void> {\n try {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO files (path, size, modified_time, hash, parent_dirs, chunks_json, errors_json)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n \n stmt.run(\n fileInfo.path,\n fileInfo.size,\n fileInfo.modifiedTime.getTime(),\n fileInfo.hash,\n JSON.stringify(fileInfo.parentDirs),\n chunks.length > 0 ? JSON.stringify(chunks) : null,\n errors.length > 0 ? JSON.stringify(errors) : null\n );\n } catch (error) {\n throw new StorageError(`Failed to upsert file record`, error as Error);\n }\n }\n\n async deleteFile(path: string): Promise<void> {\n try {\n const stmt = this.db.prepare('DELETE FROM files WHERE path = ?');\n stmt.run(path);\n } catch (error) {\n throw new StorageError(`Failed to delete file record`, error as Error);\n }\n }\n\n getDirectories(): string[] {\n const rows = this.db.prepare('SELECT path FROM directories').all() as { path: string }[];\n return rows.map(row => row.path);\n }\n\n async getFilesByDirectory(directoryPath: string): Promise<FileRecord[]> {\n try {\n const { clause, params } = directoryLikeClause('path', directoryPath);\n const stmt = this.db.prepare(`SELECT * FROM files WHERE ${clause}`);\n const rows = stmt.all(...params) as { id: number; path: string; size: number; modified_time: number; hash: string; parent_dirs: string; chunks_json: string | null; errors_json: string | null }[];\n\n return rows.map(row => ({\n id: row.id,\n path: row.path,\n size: row.size,\n modifiedTime: new Date(row.modified_time * 1000),\n hash: row.hash,\n parentDirs: JSON.parse(row.parent_dirs),\n chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],\n errors: row.errors_json ? JSON.parse(row.errors_json) : undefined\n }));\n } catch (error) {\n throw new StorageError(`Failed to get files by directory`, error as Error);\n }\n }\n\n deleteDirectory(path: string): void {\n this.db.prepare('DELETE FROM directories WHERE path = ?').run(path);\n }\n\n deleteFilesByDirectory(directoryPath: string): number {\n const { clause, params } = directoryLikeClause('path', directoryPath);\n const result = this.db.prepare(`DELETE FROM files WHERE ${clause}`).run(...params);\n return result.changes;\n }\n\n close(): void {\n openStorageInstances.delete(this);\n this.db.close();\n }\n}\n\nexport async function initializeStorage(config: Config): Promise<{ sqlite: SQLiteStorage; qdrant: QdrantClient }> {\n const sqlite = new SQLiteStorage(config);\n const qdrant = new QdrantClient(config);\n \n await qdrant.createCollection();\n \n return { sqlite, qdrant };\n}\n\nexport async function initDatabase(dbPath: string): Promise<Database.Database> {\n return new Database(dbPath);\n}\n\nexport interface ResetStats {\n sqliteExists: boolean;\n sqliteSize?: string;\n qdrantCollectionExists: boolean;\n qdrantVectorCount?: number;\n}\n\nexport interface ResetResult {\n sqliteDeleted: boolean;\n qdrantDeleted: boolean;\n warnings: string[];\n}\n\nexport async function getResetPreview(config: Config): Promise<ResetStats> {\n const stats: ResetStats = {\n sqliteExists: false,\n qdrantCollectionExists: false\n };\n\n if (await import('fs').then(fs => fs.existsSync(config.storage.sqlitePath))) {\n stats.sqliteExists = true;\n try {\n const fileStats = await import('fs').then(fs => fs.statSync(config.storage.sqlitePath));\n const sizeInMB = (fileStats.size / (1024 * 1024)).toFixed(1);\n stats.sqliteSize = `${sizeInMB} MB`;\n } catch {\n stats.sqliteSize = 'unknown size';\n }\n }\n\n try {\n const qdrant = new QdrantClient(config);\n const isHealthy = await qdrant.healthCheck();\n \n if (isHealthy) {\n const collectionInfo = await qdrant.getCollectionInfo();\n if (collectionInfo) {\n stats.qdrantCollectionExists = true;\n stats.qdrantVectorCount = collectionInfo.vectors_count || 0;\n }\n }\n } catch {\n // Qdrant unavailable\n }\n\n return stats;\n}\n\nexport async function clearDatabase(config: Config): Promise<boolean> {\n try {\n if (!await import('fs').then(fs => fs.existsSync(config.storage.sqlitePath))) {\n return true; // Already clean\n }\n \n await import('fs/promises').then(fs => fs.unlink(config.storage.sqlitePath));\n return true;\n } catch (error) {\n throw new StorageError(`Failed to delete SQLite database: ${error instanceof Error ? error.message : 'Unknown error'}`, error as Error);\n }\n}\n\nexport async function clearVectorCollection(config: Config): Promise<boolean> {\n try {\n const qdrant = new QdrantClient(config);\n const isHealthy = await qdrant.healthCheck();\n \n if (!isHealthy) {\n throw new StorageError(`Qdrant unavailable at ${config.storage.qdrantEndpoint}`);\n }\n \n const collectionInfo = await qdrant.getCollectionInfo();\n if (collectionInfo) {\n await qdrant.deleteCollection();\n }\n \n return true;\n } catch (error) {\n throw new StorageError(`Failed to reset Qdrant collection: ${error instanceof Error ? error.message : 'Unknown error'}`, error as Error);\n }\n}\n\nexport interface DirectoryStatus {\n path: string;\n status: string;\n filesCount: number;\n chunksCount: number;\n lastIndexed: string | null;\n errors: string[];\n}\n\nexport interface WorkspaceStatus {\n name: string;\n paths: string[];\n isValid: boolean;\n filesCount: number;\n chunksCount: number;\n health: {\n status: 'healthy' | 'warning' | 'error';\n issues: string[];\n recommendations: string[];\n };\n}\n\nexport interface IndexStatus {\n directoriesIndexed: number;\n filesIndexed: number;\n chunksIndexed: number;\n databaseSize: string;\n lastIndexed: string | null;\n errors: string[];\n directories: DirectoryStatus[];\n workspaces: WorkspaceStatus[];\n workspaceHealth: {\n healthy: number;\n warnings: number;\n errors: number;\n criticalIssues: string[];\n recommendations: string[];\n };\n qdrantConsistency: {\n isConsistent: boolean;\n issues: string[];\n };\n}\n\nasync function calculateWorkspaceStatistics(sqlite: SQLiteStorage, config: Config): Promise<WorkspaceStatus[]> {\n const { getAvailableWorkspaces, getWorkspacePaths } = await import('./config.js');\n const workspaces: WorkspaceStatus[] = [];\n \n // Get all indexed directories for comparison\n const directoriesStmt = sqlite.db.prepare('SELECT path, status FROM directories');\n const indexedDirectories = directoriesStmt.all() as { path: string; status: string }[];\n const indexedDirectoryPaths = indexedDirectories.map(d => d.path);\n \n // Get Qdrant client for efficient workspace filtering\n const qdrant = new QdrantClient(config);\n \n for (const workspaceName of getAvailableWorkspaces(config)) {\n const workspacePaths = getWorkspacePaths(config, workspaceName);\n const workspaceConfig = config.workspaces[workspaceName];\n \n let filesCount = 0;\n let chunksCount = 0;\n \n if (workspacePaths.length > 0) {\n try {\n // Build workspace filter using same logic as search\n const workspaceFilter = {\n must: [\n {\n key: \"parentDirectories\",\n match: { any: workspacePaths }\n }\n ]\n };\n \n // Get chunk count efficiently using Qdrant\n chunksCount = await qdrant.countPoints(workspaceFilter);\n \n // Get unique file paths using Qdrant scroll\n const points = await qdrant.scrollPoints(workspaceFilter, 10000);\n const uniqueFilePaths = new Set(points.map(p => p.payload.filePath));\n filesCount = uniqueFilePaths.size;\n \n } catch {\n // Fallback to 0 if Qdrant is unavailable\n filesCount = 0;\n chunksCount = 0;\n }\n }\n \n // Check workspace health\n const health = analyzeWorkspaceHealth(workspacePaths, workspaceConfig.isValid, filesCount, indexedDirectoryPaths);\n \n workspaces.push({\n name: workspaceName,\n paths: workspacePaths,\n isValid: workspaceConfig.isValid,\n filesCount,\n chunksCount,\n health\n });\n }\n \n return workspaces;\n}\n\nfunction analyzeWorkspaceHealth(\n workspacePaths: string[], \n isValid: boolean, \n filesCount: number, \n indexedDirectoryPaths: string[]\n): { status: 'healthy' | 'warning' | 'error'; issues: string[]; recommendations: string[] } {\n const issues: string[] = [];\n const recommendations: string[] = [];\n \n // Check if workspace paths exist and are valid\n if (!isValid) {\n issues.push('One or more workspace directories do not exist on the filesystem');\n recommendations.push('Verify workspace directory paths exist and are accessible');\n }\n \n // Check if workspace paths are indexed\n const unindexedPaths: string[] = [];\n const partiallyIndexedPaths: string[] = [];\n \n for (const workspacePath of workspacePaths) {\n // Check if this exact path is indexed\n const exactMatch = indexedDirectoryPaths.includes(workspacePath);\n \n if (exactMatch) {\n // Perfect match - workspace directory is directly indexed\n continue;\n }\n \n // Check if workspace path is covered by a parent directory that is indexed\n const isChildOfIndexed = indexedDirectoryPaths.some(indexedPath => {\n // Workspace path must start with indexed path and be a subdirectory\n return workspacePath.startsWith(indexedPath + '/') || workspacePath.startsWith(indexedPath + '\\\\');\n });\n \n if (isChildOfIndexed) {\n partiallyIndexedPaths.push(workspacePath);\n } else {\n unindexedPaths.push(workspacePath);\n }\n }\n \n // Report unindexed paths\n if (unindexedPaths.length > 0) {\n issues.push(`Workspace directories not indexed: ${unindexedPaths.join(', ')}`);\n recommendations.push(`Run indexing on these directories: ${unindexedPaths.join(', ')}`);\n }\n \n // Report partially indexed paths (where parent directory is indexed)\n if (partiallyIndexedPaths.length > 0) {\n issues.push(`Workspace directories indexed as part of parent directory: ${partiallyIndexedPaths.join(', ')}`);\n recommendations.push('Consider indexing workspace directories directly for better organization');\n }\n \n // Check if workspace is empty (no files found)\n if (isValid && filesCount === 0 && unindexedPaths.length === 0) {\n issues.push('Workspace contains no indexed files');\n recommendations.push('Verify the workspace directory contains files and re-index if necessary');\n }\n \n // Determine overall health status\n let status: 'healthy' | 'warning' | 'error';\n if (!isValid || unindexedPaths.length > 0) {\n status = 'error';\n } else if (issues.length > 0) {\n status = 'warning';\n } else {\n status = 'healthy';\n }\n \n return { status, issues, recommendations };\n}\n\nfunction calculateWorkspaceHealthSummary(workspaces: WorkspaceStatus[]): {\n healthy: number;\n warnings: number; \n errors: number;\n criticalIssues: string[];\n recommendations: string[];\n} {\n let healthy = 0;\n let warnings = 0;\n let errors = 0;\n const criticalIssues: string[] = [];\n const recommendations: string[] = [];\n \n for (const workspace of workspaces) {\n switch (workspace.health.status) {\n case 'healthy':\n healthy++;\n break;\n case 'warning':\n warnings++;\n break;\n case 'error':\n errors++;\n criticalIssues.push(`${workspace.name}: ${workspace.health.issues.join(', ')}`);\n break;\n }\n \n // Collect unique recommendations\n for (const rec of workspace.health.recommendations) {\n if (!recommendations.includes(rec)) {\n recommendations.push(rec);\n }\n }\n }\n \n return {\n healthy,\n warnings,\n errors,\n criticalIssues,\n recommendations\n };\n}\n\nasync function checkQdrantConsistency(sqlite: SQLiteStorage, config: Config): Promise<{ isConsistent: boolean; issues: string[] }> {\n const issues: string[] = [];\n \n try {\n const qdrant = new QdrantClient(config);\n const isHealthy = await qdrant.healthCheck();\n \n if (!isHealthy) {\n issues.push('Qdrant vector database is not running or accessible');\n return { isConsistent: false, issues };\n }\n \n const filesWithChunksStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM files WHERE chunks_json IS NOT NULL');\n const filesWithChunks = filesWithChunksStmt.get() as { count: number };\n \n const totalChunksStmt = sqlite.db.prepare('SELECT SUM(json_array_length(chunks_json)) as count FROM files WHERE chunks_json IS NOT NULL');\n const totalChunks = totalChunksStmt.get() as { count: number | null };\n \n if (filesWithChunks.count > 0 && (totalChunks.count || 0) === 0) {\n issues.push('Files exist but no chunks found - possible data corruption');\n }\n \n const collectionName = config.storage.qdrantCollection;\n try {\n const response = await fetch(`${config.storage.qdrantEndpoint}/collections/${collectionName}`);\n if (!response.ok) {\n issues.push(`Vector collection '${collectionName}' not found (normal during first-time setup)`);\n return { isConsistent: false, issues };\n }\n \n const collectionInfo = await response.json();\n const qdrantPointCount = collectionInfo.result?.points_count || 0;\n const sqliteChunkCount = totalChunks.count || 0;\n \n if (Math.abs(qdrantPointCount - sqliteChunkCount) > 0) {\n if (qdrantPointCount > sqliteChunkCount) {\n issues.push(`Extra vectors in database: ${qdrantPointCount} vectors vs ${sqliteChunkCount} indexed chunks (normal during cleanup)`);\n } else {\n issues.push(`Missing vectors: ${sqliteChunkCount} indexed chunks vs ${qdrantPointCount} vectors (normal during indexing)`);\n }\n }\n } catch (error) {\n issues.push(`Cannot verify vector database status: ${error}`);\n }\n \n } catch (error) {\n issues.push(`Database status check failed: ${error}`);\n }\n \n return {\n isConsistent: issues.length === 0,\n issues\n };\n}\n\nexport async function getIndexStatus(): Promise<IndexStatus> {\n const config = await import('./config.js').then(m => m.loadConfig());\n const sqlite = new SQLiteStorage(config);\n \n try {\n const directoriesStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM directories WHERE status = ?');\n const directoriesCount = directoriesStmt.get('completed') as { count: number };\n \n const filesStmt = sqlite.db.prepare('SELECT COUNT(*) as count FROM files');\n const filesCount = filesStmt.get() as { count: number };\n \n const chunksStmt = sqlite.db.prepare('SELECT SUM(json_array_length(chunks_json)) as count FROM files WHERE chunks_json IS NOT NULL');\n const chunksCount = chunksStmt.get() as { count: number | null };\n \n const lastIndexedStmt = sqlite.db.prepare('SELECT MAX(indexed_at) as last_indexed FROM directories WHERE indexed_at > 0');\n const lastIndexedResult = lastIndexedStmt.get() as { last_indexed: number | null };\n \n const errorsStmt = sqlite.db.prepare('SELECT errors_json FROM files WHERE errors_json IS NOT NULL');\n const errorRows = errorsStmt.all() as { errors_json: string }[];\n \n const allErrors: string[] = [];\n errorRows.forEach(row => {\n try {\n const errors = JSON.parse(row.errors_json);\n allErrors.push(...errors);\n } catch {\n allErrors.push('Failed to parse error JSON');\n }\n });\n \n const directoriesDetailStmt = sqlite.db.prepare(`\n SELECT\n d.path,\n d.status,\n d.indexed_at,\n (SELECT COUNT(*) FROM files f WHERE f.path = d.path OR f.path LIKE REPLACE(d.path, '\\\\', '\\\\\\\\') || '/%' ESCAPE '\\\\' OR f.path LIKE REPLACE(d.path, '\\\\', '\\\\\\\\') || '\\\\%' ESCAPE '\\\\') as files_count,\n (SELECT COALESCE(SUM(json_array_length(f.chunks_json)), 0) FROM files f WHERE (f.path = d.path OR f.path LIKE REPLACE(d.path, '\\\\', '\\\\\\\\') || '/%' ESCAPE '\\\\' OR f.path LIKE REPLACE(d.path, '\\\\', '\\\\\\\\') || '\\\\%' ESCAPE '\\\\') AND f.chunks_json IS NOT NULL) as chunks_count\n FROM directories d\n ORDER BY d.indexed_at DESC\n `);\n const directoryDetails = directoriesDetailStmt.all() as { id: number; path: string; status: 'pending' | 'indexing' | 'completed' | 'failed'; indexed_at: number; files_count: number; chunks_count: number }[];\n\n const directories: DirectoryStatus[] = directoryDetails.map(row => {\n const { clause, params } = directoryLikeClause('path', row.path);\n const errorsByDirStmt = sqlite.db.prepare(`\n SELECT errors_json FROM files\n WHERE ${clause} AND errors_json IS NOT NULL\n `);\n const dirErrors = errorsByDirStmt.all(...params) as { errors_json: string }[];\n \n const dirErrorsList: string[] = [];\n dirErrors.forEach(errorRow => {\n try {\n const errors = JSON.parse(errorRow.errors_json);\n dirErrorsList.push(...errors);\n } catch {\n dirErrorsList.push('Failed to parse error JSON');\n }\n });\n \n return {\n path: row.path,\n status: row.status,\n filesCount: row.files_count,\n chunksCount: row.chunks_count,\n lastIndexed: row.indexed_at && row.indexed_at > 0 ? new Date(row.indexed_at).toISOString() : null,\n errors: dirErrorsList\n };\n });\n \n const qdrantConsistency = await checkQdrantConsistency(sqlite, config);\n const workspaces = await calculateWorkspaceStatistics(sqlite, config);\n \n // Calculate workspace health summary\n const workspaceHealth = calculateWorkspaceHealthSummary(workspaces);\n \n const fs = await import('fs');\n let databaseSize = '0 KB';\n try {\n const stats = fs.statSync(config.storage.sqlitePath);\n const sizeInBytes = stats.size;\n if (sizeInBytes > 1024 * 1024) {\n databaseSize = `${(sizeInBytes / (1024 * 1024)).toFixed(2)} MB`;\n } else if (sizeInBytes > 1024) {\n databaseSize = `${(sizeInBytes / 1024).toFixed(2)} KB`;\n } else {\n databaseSize = `${sizeInBytes} bytes`;\n }\n } catch {\n databaseSize = 'Unknown';\n }\n \n return {\n directoriesIndexed: directoriesCount.count,\n filesIndexed: filesCount.count,\n chunksIndexed: chunksCount.count || 0,\n databaseSize,\n lastIndexed: lastIndexedResult.last_indexed ? new Date(lastIndexedResult.last_indexed).toISOString() : null,\n errors: allErrors,\n directories,\n workspaces,\n workspaceHealth,\n qdrantConsistency\n };\n } finally {\n sqlite.close();\n }\n}"],"names":[],"mappings":"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"}
package/dist/utils.js CHANGED
@@ -106,6 +106,8 @@ function isSupportedFileType(filePath) {
106
106
  ".java",
107
107
  ".cpp",
108
108
  ".c",
109
+ ".pl",
110
+ ".pm",
109
111
  ".json",
110
112
  ".yaml",
111
113
  ".yml",
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["import { createHash } from 'crypto';\nimport { promises as fs } from 'fs';\nimport { resolve, normalize, sep } from 'path';\nimport { createInterface } from 'readline';\n\nexport interface FileInfo {\n path: string;\n size: number;\n modifiedTime: Date;\n hash: string;\n parentDirs: string[];\n}\n\nexport interface ChunkInfo {\n id: string;\n content: string;\n startIndex: number;\n endIndex: number;\n}\n\nexport class FileError extends Error {\n constructor(message: string, public filePath: string, public override cause?: Error) {\n super(message);\n this.name = 'FileError';\n }\n}\n\nexport function normalizePath(path: string): string {\n return normalize(resolve(path));\n}\n\nexport function getParentDirectories(filePath: string): string[] {\n const normalizedPath = normalizePath(filePath);\n const parts = normalizedPath.split(sep);\n const parents: string[] = [];\n \n for (let i = 1; i < parts.length; i++) {\n parents.push(parts.slice(0, i + 1).join(sep));\n }\n \n return parents;\n}\n\nexport async function getFileHash(filePath: string): Promise<string> {\n try {\n const content = await fs.readFile(filePath);\n return createHash('sha256').update(content).digest('hex');\n } catch (error) {\n throw new FileError(`Failed to hash file`, filePath, error as Error);\n }\n}\n\nexport function calculateHash(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nexport async function getFileInfo(filePath: string): Promise<FileInfo> {\n try {\n const stats = await fs.stat(filePath);\n const hash = await getFileHash(filePath);\n const parentDirs = getParentDirectories(filePath);\n \n return {\n path: normalizePath(filePath),\n size: stats.size,\n modifiedTime: stats.mtime,\n hash,\n parentDirs,\n };\n } catch (error) {\n throw new FileError(`Failed to get file info`, filePath, error as Error);\n }\n}\n\nexport async function isDirectory(path: string): Promise<boolean> {\n try {\n const stats = await fs.stat(path);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\nexport async function isFile(path: string): Promise<boolean> {\n try {\n const stats = await fs.stat(path);\n return stats.isFile();\n } catch {\n return false;\n }\n}\n\nexport async function fileExists(path: string): Promise<boolean> {\n try {\n await fs.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureDirectory(dirPath: string): Promise<void> {\n try {\n await fs.mkdir(dirPath, { recursive: true });\n } catch (error) {\n throw new FileError(`Failed to create directory`, dirPath, error as Error);\n }\n}\n\nexport function shouldIgnoreFile(\n filePath: string, \n relativePath: string,\n ignorePatterns: string[],\n gitignoreFilter?: { ignores: (path: string) => boolean } | null\n): boolean {\n const normalizedPath = normalizePath(filePath);\n \n // Essential patterns always take precedence\n if (ignorePatterns.some(pattern => normalizedPath.includes(pattern))) {\n return true;\n }\n \n // Check gitignore patterns using relative path\n if (gitignoreFilter && relativePath) {\n try {\n return gitignoreFilter.ignores(relativePath);\n } catch {\n // Ignore errors in gitignore matching\n }\n }\n \n return false;\n}\n\nexport function isSupportedFileType(filePath: string): boolean {\n const supportedExtensions = [\n '.md', '.txt', '.rst',\n '.rs', '.py', '.js', '.ts', '.go', '.java', '.cpp', '.c',\n '.json', '.yaml', '.yml', '.toml', '.csv',\n '.env', '.conf', '.ini',\n '.html', '.xml'\n ];\n \n return supportedExtensions.some(ext => filePath.toLowerCase().endsWith(ext));\n}\n\nexport async function readlineSync(prompt: string): Promise<string> {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout\n });\n\n return new Promise((resolve) => {\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer);\n });\n });\n}"],"names":["fs","resolve"],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sources":["../src/utils.ts"],"sourcesContent":["import { createHash } from 'crypto';\nimport { promises as fs } from 'fs';\nimport { resolve, normalize, sep } from 'path';\nimport { createInterface } from 'readline';\n\nexport interface FileInfo {\n path: string;\n size: number;\n modifiedTime: Date;\n hash: string;\n parentDirs: string[];\n}\n\nexport interface ChunkInfo {\n id: string;\n content: string;\n startIndex: number;\n endIndex: number;\n}\n\nexport class FileError extends Error {\n constructor(message: string, public filePath: string, public override cause?: Error) {\n super(message);\n this.name = 'FileError';\n }\n}\n\nexport function normalizePath(path: string): string {\n return normalize(resolve(path));\n}\n\nexport function getParentDirectories(filePath: string): string[] {\n const normalizedPath = normalizePath(filePath);\n const parts = normalizedPath.split(sep);\n const parents: string[] = [];\n \n for (let i = 1; i < parts.length; i++) {\n parents.push(parts.slice(0, i + 1).join(sep));\n }\n \n return parents;\n}\n\nexport async function getFileHash(filePath: string): Promise<string> {\n try {\n const content = await fs.readFile(filePath);\n return createHash('sha256').update(content).digest('hex');\n } catch (error) {\n throw new FileError(`Failed to hash file`, filePath, error as Error);\n }\n}\n\nexport function calculateHash(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nexport async function getFileInfo(filePath: string): Promise<FileInfo> {\n try {\n const stats = await fs.stat(filePath);\n const hash = await getFileHash(filePath);\n const parentDirs = getParentDirectories(filePath);\n \n return {\n path: normalizePath(filePath),\n size: stats.size,\n modifiedTime: stats.mtime,\n hash,\n parentDirs,\n };\n } catch (error) {\n throw new FileError(`Failed to get file info`, filePath, error as Error);\n }\n}\n\nexport async function isDirectory(path: string): Promise<boolean> {\n try {\n const stats = await fs.stat(path);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\nexport async function isFile(path: string): Promise<boolean> {\n try {\n const stats = await fs.stat(path);\n return stats.isFile();\n } catch {\n return false;\n }\n}\n\nexport async function fileExists(path: string): Promise<boolean> {\n try {\n await fs.access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureDirectory(dirPath: string): Promise<void> {\n try {\n await fs.mkdir(dirPath, { recursive: true });\n } catch (error) {\n throw new FileError(`Failed to create directory`, dirPath, error as Error);\n }\n}\n\nexport function shouldIgnoreFile(\n filePath: string, \n relativePath: string,\n ignorePatterns: string[],\n gitignoreFilter?: { ignores: (path: string) => boolean } | null\n): boolean {\n const normalizedPath = normalizePath(filePath);\n \n // Essential patterns always take precedence\n if (ignorePatterns.some(pattern => normalizedPath.includes(pattern))) {\n return true;\n }\n \n // Check gitignore patterns using relative path\n if (gitignoreFilter && relativePath) {\n try {\n return gitignoreFilter.ignores(relativePath);\n } catch {\n // Ignore errors in gitignore matching\n }\n }\n \n return false;\n}\n\nexport function isSupportedFileType(filePath: string): boolean {\n const supportedExtensions = [\n '.md', '.txt', '.rst',\n '.rs', '.py', '.js', '.ts', '.go', '.java', '.cpp', '.c', '.pl', '.pm',\n '.json', '.yaml', '.yml', '.toml', '.csv',\n '.env', '.conf', '.ini',\n '.html', '.xml'\n ];\n \n return supportedExtensions.some(ext => filePath.toLowerCase().endsWith(ext));\n}\n\nexport async function readlineSync(prompt: string): Promise<string> {\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout\n });\n\n return new Promise((resolve) => {\n rl.question(prompt, (answer) => {\n rl.close();\n resolve(answer);\n });\n });\n}\n"],"names":["fs","resolve"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directory-indexer",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "AI-powered directory indexing with semantic search for MCP servers",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
@@ -42,29 +42,33 @@
42
42
  "engines": {
43
43
  "node": ">=18.0.0"
44
44
  },
45
+ "publishConfig": {
46
+ "provenance": true,
47
+ "access": "public"
48
+ },
45
49
  "files": [
46
50
  "dist/",
47
51
  "bin/"
48
52
  ],
49
53
  "dependencies": {
50
- "@modelcontextprotocol/sdk": "^0.6.0",
54
+ "@modelcontextprotocol/sdk": "^1.27.1",
51
55
  "better-sqlite3": "^11.5.0",
52
56
  "commander": "^12.1.0",
53
57
  "ignore": "^7.0.5",
54
58
  "mime-types": "^2.1.35",
55
- "zod": "^3.23.8"
59
+ "zod": "^3.25.0"
56
60
  },
57
61
  "devDependencies": {
58
62
  "@types/better-sqlite3": "^7.6.11",
59
63
  "@types/mime-types": "^2.1.4",
60
64
  "@types/node": "^22.10.1",
61
- "@typescript-eslint/eslint-plugin": "^8.15.0",
62
- "@typescript-eslint/parser": "^8.15.0",
65
+ "@typescript-eslint/eslint-plugin": "^8.53.0",
66
+ "@typescript-eslint/parser": "^8.53.0",
63
67
  "@vitest/coverage-v8": "^3.2.4",
64
- "eslint": "^9.15.0",
65
- "tmp": "^0.2.3",
66
- "typescript": "^5.7.2",
67
- "vite": "^6.0.3",
68
+ "eslint": "^9.39.2",
69
+ "tmp": "^0.2.4",
70
+ "typescript": "^5.9.3",
71
+ "vite": "^6.4.1",
68
72
  "vitest": "^3.2.4"
69
73
  }
70
- }
74
+ }