kotadb 2.0.1-next.20260203174555 → 2.0.1-next.20260203184744

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kotadb",
3
- "version": "2.0.1-next.20260203174555",
3
+ "version": "2.0.1-next.20260203184744",
4
4
  "description": "Local-only code intelligence tool for CLI agents. SQLite-backed repository indexing and code search via MCP.",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
@@ -57,6 +57,7 @@
57
57
  "@typescript-eslint/parser": "^8.0.0",
58
58
  "@typescript-eslint/types": "^8.54.0",
59
59
  "bcryptjs": "^2.4.3",
60
+ "chokidar": "^5.0.0",
60
61
  "cors": "^2.8.6",
61
62
  "express": "^4.18.0",
62
63
  "zod": "^4.3.6"
@@ -1066,6 +1066,180 @@ export async function runIndexingWorkflow(
1066
1066
  };
1067
1067
  }
1068
1068
 
1069
+ // ============================================================================
1070
+ // Repository Indexing Status & File Deletion Operations
1071
+ // ============================================================================
1072
+
1073
+ /**
1074
+ * Check if a repository has been indexed (has files in indexed_files table).
1075
+ *
1076
+ * @param repositoryId - Repository UUID or full_name
1077
+ * @returns true if the repository has indexed files, false otherwise
1078
+ */
1079
+ function isRepositoryIndexedInternal(
1080
+ db: KotaDatabase,
1081
+ repositoryId: string,
1082
+ ): boolean {
1083
+ // First try to match by ID, then by full_name
1084
+ const result = db.queryOne<{ count: number }>(
1085
+ `SELECT COUNT(*) as count FROM indexed_files
1086
+ WHERE repository_id = ?
1087
+ OR repository_id IN (SELECT id FROM repositories WHERE full_name = ?)`,
1088
+ [repositoryId, repositoryId]
1089
+ );
1090
+ return (result?.count ?? 0) > 0;
1091
+ }
1092
+
1093
+ /**
1094
+ * Check if a repository has been indexed.
1095
+ *
1096
+ * @param repositoryId - Repository UUID or full_name
1097
+ * @returns true if the repository has indexed files, false otherwise
1098
+ */
1099
+ export function isRepositoryIndexed(repositoryId: string): boolean {
1100
+ return isRepositoryIndexedInternal(getGlobalDatabase(), repositoryId);
1101
+ }
1102
+
1103
+ /**
1104
+ * Delete a single file from the index by path.
1105
+ * Cascading deletes will remove associated symbols and references.
1106
+ *
1107
+ * @param repositoryId - Repository UUID
1108
+ * @param filePath - Relative file path to delete
1109
+ * @returns true if file was deleted, false if not found
1110
+ */
1111
+ function deleteFileByPathInternal(
1112
+ db: KotaDatabase,
1113
+ repositoryId: string,
1114
+ filePath: string,
1115
+ ): boolean {
1116
+ const normalizedPath = normalizePath(filePath);
1117
+
1118
+ // The indexed_files table has ON DELETE CASCADE for:
1119
+ // - indexed_symbols (via file_id FK)
1120
+ // - indexed_references (via file_id FK)
1121
+ // FTS5 triggers handle indexed_files_fts cleanup automatically
1122
+
1123
+ const result = db.queryOne<{ id: string }>(
1124
+ `SELECT id FROM indexed_files WHERE repository_id = ? AND path = ?`,
1125
+ [repositoryId, normalizedPath]
1126
+ );
1127
+
1128
+ if (!result) {
1129
+ logger.debug("File not found for deletion", { repositoryId, filePath: normalizedPath });
1130
+ return false;
1131
+ }
1132
+
1133
+ db.run(
1134
+ `DELETE FROM indexed_files WHERE id = ?`,
1135
+ [result.id]
1136
+ );
1137
+
1138
+ logger.info("Deleted file from index", { repositoryId, filePath: normalizedPath, fileId: result.id });
1139
+ return true;
1140
+ }
1141
+
1142
+ /**
1143
+ * Delete a single file from the index by path.
1144
+ *
1145
+ * @param repositoryId - Repository UUID
1146
+ * @param filePath - Relative file path to delete
1147
+ * @returns true if file was deleted, false if not found
1148
+ */
1149
+ export function deleteFileByPath(
1150
+ repositoryId: string,
1151
+ filePath: string,
1152
+ ): boolean {
1153
+ return deleteFileByPathInternal(getGlobalDatabase(), repositoryId, filePath);
1154
+ }
1155
+
1156
+ /**
1157
+ * Delete multiple files from the index by paths.
1158
+ * Uses a transaction for atomic operation.
1159
+ * Cascading deletes will remove associated symbols and references.
1160
+ *
1161
+ * @param repositoryId - Repository UUID
1162
+ * @param filePaths - Array of relative file paths to delete
1163
+ * @returns Object with deleted count and list of deleted paths
1164
+ */
1165
+ function deleteFilesByPathsInternal(
1166
+ db: KotaDatabase,
1167
+ repositoryId: string,
1168
+ filePaths: string[],
1169
+ ): { deletedCount: number; deletedPaths: string[] } {
1170
+ if (filePaths.length === 0) {
1171
+ return { deletedCount: 0, deletedPaths: [] };
1172
+ }
1173
+
1174
+ const normalizedPaths = filePaths.map(normalizePath);
1175
+ const deletedPaths: string[] = [];
1176
+
1177
+ db.transaction(() => {
1178
+ for (const normalizedPath of normalizedPaths) {
1179
+ const result = db.queryOne<{ id: string }>(
1180
+ `SELECT id FROM indexed_files WHERE repository_id = ? AND path = ?`,
1181
+ [repositoryId, normalizedPath]
1182
+ );
1183
+
1184
+ if (result) {
1185
+ db.run(`DELETE FROM indexed_files WHERE id = ?`, [result.id]);
1186
+ deletedPaths.push(normalizedPath);
1187
+ }
1188
+ }
1189
+ });
1190
+
1191
+ logger.info("Deleted files from index", {
1192
+ repositoryId,
1193
+ requestedCount: filePaths.length,
1194
+ deletedCount: deletedPaths.length
1195
+ });
1196
+
1197
+ return { deletedCount: deletedPaths.length, deletedPaths };
1198
+ }
1199
+
1200
+ /**
1201
+ * Delete multiple files from the index by paths.
1202
+ *
1203
+ * @param repositoryId - Repository UUID
1204
+ * @param filePaths - Array of relative file paths to delete
1205
+ * @returns Object with deleted count and list of deleted paths
1206
+ */
1207
+ export function deleteFilesByPaths(
1208
+ repositoryId: string,
1209
+ filePaths: string[],
1210
+ ): { deletedCount: number; deletedPaths: string[] } {
1211
+ return deleteFilesByPathsInternal(getGlobalDatabase(), repositoryId, filePaths);
1212
+ }
1213
+
1214
+ /**
1215
+ * Get repository ID from full_name.
1216
+ * Useful for auto-indexing when you have the path but need the UUID.
1217
+ *
1218
+ * @param fullName - Repository full name (e.g., "owner/repo" or "local/path")
1219
+ * @returns Repository UUID or null if not found
1220
+ */
1221
+ function getRepositoryIdByNameInternal(
1222
+ db: KotaDatabase,
1223
+ fullName: string,
1224
+ ): string | null {
1225
+ const result = db.queryOne<{ id: string }>(
1226
+ `SELECT id FROM repositories WHERE full_name = ?`,
1227
+ [fullName]
1228
+ );
1229
+ return result?.id ?? null;
1230
+ }
1231
+
1232
+ /**
1233
+ * Get repository ID from full_name.
1234
+ *
1235
+ * @param fullName - Repository full name
1236
+ * @returns Repository UUID or null if not found
1237
+ */
1238
+ export function getRepositoryIdByName(fullName: string): string | null {
1239
+ return getRepositoryIdByNameInternal(getGlobalDatabase(), fullName);
1240
+ }
1241
+
1242
+
1069
1243
  // ============================================================================
1070
1244
  // Backward-compatible aliases that accept db parameter
1071
1245
  // These use the passed database (for tests) rather than the global one
@@ -1172,6 +1346,52 @@ export function updateRepositoryLastIndexedLocal(
1172
1346
  return updateRepositoryLastIndexedInternal(db, repositoryId);
1173
1347
  }
1174
1348
 
1349
+ /**
1350
+ * Check if a repository has been indexed.
1351
+ * Version that accepts db parameter for testing.
1352
+ */
1353
+ export function isRepositoryIndexedLocal(
1354
+ db: KotaDatabase,
1355
+ repositoryId: string
1356
+ ): boolean {
1357
+ return isRepositoryIndexedInternal(db, repositoryId);
1358
+ }
1359
+
1360
+ /**
1361
+ * Delete a single file from the index by path.
1362
+ * Version that accepts db parameter for testing.
1363
+ */
1364
+ export function deleteFileByPathLocal(
1365
+ db: KotaDatabase,
1366
+ repositoryId: string,
1367
+ filePath: string
1368
+ ): boolean {
1369
+ return deleteFileByPathInternal(db, repositoryId, filePath);
1370
+ }
1371
+
1372
+ /**
1373
+ * Delete multiple files from the index by paths.
1374
+ * Version that accepts db parameter for testing.
1375
+ */
1376
+ export function deleteFilesByPathsLocal(
1377
+ db: KotaDatabase,
1378
+ repositoryId: string,
1379
+ filePaths: string[]
1380
+ ): { deletedCount: number; deletedPaths: string[] } {
1381
+ return deleteFilesByPathsInternal(db, repositoryId, filePaths);
1382
+ }
1383
+
1384
+ /**
1385
+ * Get repository ID from full_name.
1386
+ * Version that accepts db parameter for testing.
1387
+ */
1388
+ export function getRepositoryIdByNameLocal(
1389
+ db: KotaDatabase,
1390
+ fullName: string
1391
+ ): string | null {
1392
+ return getRepositoryIdByNameInternal(db, fullName);
1393
+ }
1394
+
1175
1395
  // Add alias for runIndexingWorkflowLocal
1176
1396
  export const runIndexingWorkflowLocal = runIndexingWorkflow;
1177
1397