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/cli-handlers.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/indexing.js +10 -2
- package/dist/indexing.js.map +1 -1
- package/dist/logger.js +43 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp-handlers.js +215 -42
- package/dist/mcp-handlers.js.map +1 -1
- package/dist/mcp.js +200 -106
- package/dist/mcp.js.map +1 -1
- package/dist/path-validation.js +47 -0
- package/dist/path-validation.js.map +1 -0
- package/dist/storage.js +46 -8
- package/dist/storage.js.map +1 -1
- package/dist/utils.js +2 -0
- package/dist/utils.js.map +1 -1
- package/package.json +14 -10
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
|
|
333
|
-
const
|
|
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 || '
|
|
611
|
-
(SELECT COALESCE(SUM(json_array_length(f.chunks_json)), 0) FROM files f WHERE f.path LIKE d.path || '
|
|
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
|
|
655
|
+
SELECT errors_json FROM files
|
|
656
|
+
WHERE ${clause} AND errors_json IS NOT NULL
|
|
620
657
|
`);
|
|
621
|
-
const dirErrors = errorsByDirStmt.all(
|
|
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,
|
package/dist/storage.js.map
CHANGED
|
@@ -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
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":";;;;AAoBO,MAAM,kBAAkB,MAAM;AAAA,EACnC,YAAY,SAAwB,UAAkC,OAAe;AACnF,UAAM,OAAO;AADqB,SAAA,WAAA;AAAkC,SAAA,QAAA;AAEpE,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,cAAc,MAAsB;AAClD,SAAO,UAAU,QAAQ,IAAI,CAAC;AAChC;AAEO,SAAS,qBAAqB,UAA4B;AAC/D,QAAM,iBAAiB,cAAc,QAAQ;AAC7C,QAAM,QAAQ,eAAe,MAAM,GAAG;AACtC,QAAM,UAAoB,CAAA;AAE1B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAQ,KAAK,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,EAC9C;AAEA,SAAO;AACT;AAEA,eAAsB,YAAY,UAAmC;AACnE,MAAI;AACF,UAAM,UAAU,MAAMA,SAAG,SAAS,QAAQ;AAC1C,WAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAAA,EAC1D,SAAS,OAAO;AACd,UAAM,IAAI,UAAU,uBAAuB,UAAU,KAAc;AAAA,EACrE;AACF;AAEO,SAAS,cAAc,SAAyB;AACrD,SAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC1D;AAEA,eAAsB,YAAY,UAAqC;AACrE,MAAI;AACF,UAAM,QAAQ,MAAMA,SAAG,KAAK,QAAQ;AACpC,UAAM,OAAO,MAAM,YAAY,QAAQ;AACvC,UAAM,aAAa,qBAAqB,QAAQ;AAEhD,WAAO;AAAA,MACL,MAAM,cAAc,QAAQ;AAAA,MAC5B,MAAM,MAAM;AAAA,MACZ,cAAc,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ,SAAS,OAAO;AACd,UAAM,IAAI,UAAU,2BAA2B,UAAU,KAAc;AAAA,EACzE;AACF;AAEA,eAAsB,YAAY,MAAgC;AAChE,MAAI;AACF,UAAM,QAAQ,MAAMA,SAAG,KAAK,IAAI;AAChC,WAAO,MAAM,YAAA;AAAA,EACf,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,OAAO,MAAgC;AAC3D,MAAI;AACF,UAAM,QAAQ,MAAMA,SAAG,KAAK,IAAI;AAChC,WAAO,MAAM,OAAA;AAAA,EACf,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,WAAW,MAAgC;AAC/D,MAAI;AACF,UAAMA,SAAG,OAAO,IAAI;AACpB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,gBAAgB,SAAgC;AACpE,MAAI;AACF,UAAMA,SAAG,MAAM,SAAS,EAAE,WAAW,MAAM;AAAA,EAC7C,SAAS,OAAO;AACd,UAAM,IAAI,UAAU,8BAA8B,SAAS,KAAc;AAAA,EAC3E;AACF;AAEO,SAAS,iBACd,UACA,cACA,gBACA,iBACS;AACT,QAAM,iBAAiB,cAAc,QAAQ;AAG7C,MAAI,eAAe,KAAK,CAAA,YAAW,eAAe,SAAS,OAAO,CAAC,GAAG;AACpE,WAAO;AAAA,EACT;AAGA,MAAI,mBAAmB,cAAc;AACnC,QAAI;AACF,aAAO,gBAAgB,QAAQ,YAAY;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,oBAAoB,UAA2B;AAC7D,QAAM,sBAAsB;AAAA,IAC1B;AAAA,IAAO;AAAA,IAAQ;AAAA,IACf;AAAA,IAAO;AAAA,IAAO;AAAA,IAAO;AAAA,IAAO;AAAA,IAAO;AAAA,IAAS;AAAA,IAAQ;AAAA,
|
|
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.
|
|
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": "^
|
|
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.
|
|
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.
|
|
62
|
-
"@typescript-eslint/parser": "^8.
|
|
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.
|
|
65
|
-
"tmp": "^0.2.
|
|
66
|
-
"typescript": "^5.
|
|
67
|
-
"vite": "^6.
|
|
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
|
+
}
|