@jagilber-org/index-server 1.22.0 → 1.26.1
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/CHANGELOG.md +87 -2
- package/CODE_OF_CONDUCT.md +2 -0
- package/CONTRIBUTING.md +32 -2
- package/README.md +83 -20
- package/SECURITY.md +17 -5
- package/dist/config/dashboardConfig.d.ts +3 -0
- package/dist/config/dashboardConfig.js +3 -0
- package/dist/config/defaultValues.d.ts +1 -1
- package/dist/config/defaultValues.js +1 -1
- package/dist/config/featureConfig.d.ts +2 -0
- package/dist/config/featureConfig.js +6 -1
- package/dist/config/runtimeConfig.d.ts +1 -1
- package/dist/config/runtimeConfig.js +8 -9
- package/dist/dashboard/client/admin.html +173 -54
- package/dist/dashboard/client/css/admin.css +151 -0
- package/dist/dashboard/client/js/admin.auth.js +25 -11
- package/dist/dashboard/client/js/admin.config.js +1 -1
- package/dist/dashboard/client/js/admin.feedback.js +328 -0
- package/dist/dashboard/client/js/admin.graph.js +120 -18
- package/dist/dashboard/client/js/admin.instructions.js +27 -13
- package/dist/dashboard/client/js/admin.logs.js +1 -5
- package/dist/dashboard/client/js/admin.maintenance.js +53 -8
- package/dist/dashboard/client/js/admin.messaging.js +1 -4
- package/dist/dashboard/client/js/admin.overview.js +5 -1
- package/dist/dashboard/client/js/admin.sessions.js +1 -1
- package/dist/dashboard/client/js/admin.utils.js +43 -1
- package/dist/dashboard/client/js/mermaid.min.js +813 -537
- package/dist/dashboard/export/DataExporter.js +2 -1
- package/dist/dashboard/server/AdminPanel.d.ts +3 -0
- package/dist/dashboard/server/AdminPanel.js +132 -35
- package/dist/dashboard/server/ApiRoutes.js +40 -9
- package/dist/dashboard/server/DashboardServer.js +1 -1
- package/dist/dashboard/server/FileMetricsStorage.d.ts +19 -0
- package/dist/dashboard/server/FileMetricsStorage.js +52 -5
- package/dist/dashboard/server/HttpTransport.js +6 -0
- package/dist/dashboard/server/InstanceManager.js +7 -2
- package/dist/dashboard/server/KnowledgeStore.js +7 -2
- package/dist/dashboard/server/MetricsCollector.d.ts +16 -0
- package/dist/dashboard/server/MetricsCollector.js +113 -17
- package/dist/dashboard/server/legacyDashboardHtml.js +7 -2
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +8 -3
- package/dist/dashboard/server/routes/admin.feedback.routes.d.ts +15 -0
- package/dist/dashboard/server/routes/admin.feedback.routes.js +188 -0
- package/dist/dashboard/server/routes/admin.routes.js +35 -27
- package/dist/dashboard/server/routes/alerts.routes.js +4 -3
- package/dist/dashboard/server/routes/api.feedback.routes.js +2 -1
- package/dist/dashboard/server/routes/api.usage.routes.js +8 -7
- package/dist/dashboard/server/routes/embeddings.routes.d.ts +2 -1
- package/dist/dashboard/server/routes/embeddings.routes.js +18 -9
- package/dist/dashboard/server/routes/graph.routes.js +10 -13
- package/dist/dashboard/server/routes/index.d.ts +1 -0
- package/dist/dashboard/server/routes/index.js +74 -39
- package/dist/dashboard/server/routes/instances.routes.js +2 -1
- package/dist/dashboard/server/routes/instructions.routes.js +46 -27
- package/dist/dashboard/server/routes/knowledge.routes.js +4 -3
- package/dist/dashboard/server/routes/logs.routes.js +5 -4
- package/dist/dashboard/server/routes/messaging.routes.js +15 -14
- package/dist/dashboard/server/routes/metrics.routes.js +14 -13
- package/dist/dashboard/server/routes/scripts.routes.js +6 -3
- package/dist/dashboard/server/routes/status.routes.js +25 -6
- package/dist/dashboard/server/routes/synthetic.routes.js +3 -2
- package/dist/dashboard/server/routes/usage.routes.js +2 -1
- package/dist/dashboard/server/utils/escapeHtml.d.ts +1 -0
- package/dist/dashboard/server/utils/escapeHtml.js +11 -0
- package/dist/dashboard/server/utils/pathContainment.d.ts +1 -0
- package/dist/dashboard/server/utils/pathContainment.js +15 -0
- package/dist/dashboard/server/wsInit.js +2 -2
- package/dist/lib/mcpStdioLogging.d.ts +165 -0
- package/dist/lib/mcpStdioLogging.js +287 -0
- package/dist/schemas/index.d.ts +37 -2
- package/dist/schemas/index.js +27 -3
- package/dist/server/backgroundServicesStartup.d.ts +7 -1
- package/dist/server/backgroundServicesStartup.js +25 -8
- package/dist/server/certInit.d.ts +97 -0
- package/dist/server/certInit.js +359 -0
- package/dist/server/certInit.types.d.ts +92 -0
- package/dist/server/certInit.types.js +34 -0
- package/dist/server/handshake/fallbackFrames.d.ts +31 -0
- package/dist/server/handshake/fallbackFrames.js +38 -0
- package/dist/server/handshake/initializeDetector.d.ts +31 -0
- package/dist/server/handshake/initializeDetector.js +88 -0
- package/dist/server/handshake/protocol.d.ts +15 -0
- package/dist/server/handshake/protocol.js +37 -0
- package/dist/server/handshake/readyEmitter.d.ts +6 -0
- package/dist/server/handshake/readyEmitter.js +88 -0
- package/dist/server/handshake/safetyFallbacks.d.ts +1 -0
- package/dist/server/handshake/safetyFallbacks.js +134 -0
- package/dist/server/handshake/stdinSniffer.d.ts +1 -0
- package/dist/server/handshake/stdinSniffer.js +260 -0
- package/dist/server/handshake/tracing.d.ts +16 -0
- package/dist/server/handshake/tracing.js +95 -0
- package/dist/server/handshakeManager.d.ts +23 -23
- package/dist/server/handshakeManager.js +36 -466
- package/dist/server/index-server.d.ts +23 -0
- package/dist/server/index-server.js +194 -9
- package/dist/server/mcpReadOnlySurfaces.d.ts +44 -0
- package/dist/server/mcpReadOnlySurfaces.js +297 -0
- package/dist/server/sdkServer.js +69 -7
- package/dist/server/transport.d.ts +5 -6
- package/dist/server/transport.js +46 -64
- package/dist/server/transportFactory.d.ts +3 -9
- package/dist/server/transportFactory.js +18 -380
- package/dist/services/atomicFs.d.ts +3 -0
- package/dist/services/atomicFs.js +171 -13
- package/dist/services/auditLog.d.ts +17 -2
- package/dist/services/auditLog.js +75 -14
- package/dist/services/bootstrapGating.js +1 -1
- package/dist/services/categoryRules.d.ts +10 -0
- package/dist/services/categoryRules.js +17 -0
- package/dist/services/classificationService.js +7 -5
- package/dist/services/embeddingService.d.ts +27 -11
- package/dist/services/embeddingService.js +51 -14
- package/dist/services/feedbackStorage.d.ts +39 -0
- package/dist/services/feedbackStorage.js +88 -0
- package/dist/services/handlers/instructions.add.js +429 -317
- package/dist/services/handlers/instructions.groom.js +128 -31
- package/dist/services/handlers/instructions.import.js +56 -23
- package/dist/services/handlers/instructions.patch.js +43 -32
- package/dist/services/handlers/instructions.query.js +20 -29
- package/dist/services/handlers/instructions.shared.d.ts +54 -0
- package/dist/services/handlers/instructions.shared.js +126 -1
- package/dist/services/handlers.activation.js +83 -81
- package/dist/services/handlers.dashboardConfig.d.ts +2 -2
- package/dist/services/handlers.dashboardConfig.js +1 -2
- package/dist/services/handlers.diagnostics.js +75 -54
- package/dist/services/handlers.feedback.d.ts +4 -11
- package/dist/services/handlers.feedback.js +11 -333
- package/dist/services/handlers.gates.js +69 -37
- package/dist/services/handlers.graph.js +2 -2
- package/dist/services/handlers.help.js +2 -2
- package/dist/services/handlers.instructionSchema.js +4 -2
- package/dist/services/handlers.integrity.js +42 -22
- package/dist/services/handlers.messaging.js +1 -1
- package/dist/services/handlers.metrics.js +51 -6
- package/dist/services/handlers.prompt.js +10 -2
- package/dist/services/handlers.search.js +94 -44
- package/dist/services/handlers.trace.js +1 -1
- package/dist/services/handlers.usage.js +38 -7
- package/dist/services/indexContext.d.ts +21 -1
- package/dist/services/indexContext.js +267 -82
- package/dist/services/indexLoader.d.ts +1 -0
- package/dist/services/indexLoader.js +28 -8
- package/dist/services/instructionRecordValidation.d.ts +39 -0
- package/dist/services/instructionRecordValidation.js +388 -0
- package/dist/services/instructions.dispatcher.js +4 -4
- package/dist/services/loaderSchemaValidator.d.ts +15 -0
- package/dist/services/loaderSchemaValidator.js +69 -0
- package/dist/services/logger.js +11 -2
- package/dist/services/mcpLogBridge.d.ts +49 -0
- package/dist/services/mcpLogBridge.js +83 -0
- package/dist/services/ownershipService.js +18 -8
- package/dist/services/performanceBaseline.js +23 -22
- package/dist/services/promptReviewService.d.ts +3 -1
- package/dist/services/promptReviewService.js +41 -13
- package/dist/services/regexSafety.d.ts +6 -0
- package/dist/services/regexSafety.js +46 -0
- package/dist/services/seedBootstrap.js +4 -4
- package/dist/services/storage/factory.d.ts +14 -1
- package/dist/services/storage/factory.js +61 -1
- package/dist/services/storage/jsonEmbeddingStore.d.ts +15 -0
- package/dist/services/storage/jsonEmbeddingStore.js +83 -0
- package/dist/services/storage/jsonFileStore.d.ts +3 -1
- package/dist/services/storage/jsonFileStore.js +8 -6
- package/dist/services/storage/migrationEngine.d.ts +13 -0
- package/dist/services/storage/migrationEngine.js +31 -0
- package/dist/services/storage/sqliteEmbeddingStore.d.ts +30 -0
- package/dist/services/storage/sqliteEmbeddingStore.js +222 -0
- package/dist/services/storage/sqliteStore.d.ts +3 -1
- package/dist/services/storage/sqliteStore.js +2 -2
- package/dist/services/storage/types.d.ts +48 -1
- package/dist/services/toolRegistry.js +77 -67
- package/dist/services/toolRegistry.zod.js +89 -86
- package/dist/services/tracing.js +5 -4
- package/dist/utils/envUtils.d.ts +4 -0
- package/dist/utils/envUtils.js +7 -0
- package/dist/utils/memoryMonitor.js +11 -10
- package/package.json +11 -4
- package/schemas/instruction.schema.json +38 -1
- package/scripts/copy-dashboard-assets.mjs +1 -1
- package/scripts/dist/README.md +1 -1
- package/scripts/setup-wizard.mjs +781 -0
- package/server.json +1 -0
- package/dist/externalClientLib.d.ts +0 -1
- package/dist/externalClientLib.js +0 -2
- package/dist/portableClientWrapper.d.ts +0 -1
- package/dist/portableClientWrapper.js +0 -2
- package/dist/services/indexingService.d.ts +0 -1
- package/dist/services/indexingService.js +0 -2
|
@@ -19,7 +19,9 @@ export declare class JsonFileStore implements IInstructionStore {
|
|
|
19
19
|
load(): LoadResult;
|
|
20
20
|
close(): void;
|
|
21
21
|
get(id: string): InstructionEntry | null;
|
|
22
|
-
write(entry: InstructionEntry
|
|
22
|
+
write(entry: InstructionEntry, opts?: {
|
|
23
|
+
createOnly?: boolean;
|
|
24
|
+
}): void;
|
|
23
25
|
remove(id: string): void;
|
|
24
26
|
list(opts?: ListOptions): InstructionEntry[];
|
|
25
27
|
query(opts: QueryOptions): InstructionEntry[];
|
|
@@ -14,8 +14,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.JsonFileStore = void 0;
|
|
15
15
|
const fs_1 = __importDefault(require("fs"));
|
|
16
16
|
const path_1 = __importDefault(require("path"));
|
|
17
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
18
17
|
const hashUtils_js_1 = require("./hashUtils.js");
|
|
18
|
+
const atomicFs_js_1 = require("../atomicFs.js");
|
|
19
19
|
class JsonFileStore {
|
|
20
20
|
dir;
|
|
21
21
|
cache = new Map();
|
|
@@ -87,12 +87,14 @@ class JsonFileStore {
|
|
|
87
87
|
this.ensureLoaded();
|
|
88
88
|
return this.cache.get(id) ?? null;
|
|
89
89
|
}
|
|
90
|
-
write(entry) {
|
|
91
|
-
// Write to disk
|
|
90
|
+
write(entry, opts) {
|
|
92
91
|
const filePath = path_1.default.join(this.dir, `${entry.id}.json`);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
if (opts?.createOnly) {
|
|
93
|
+
(0, atomicFs_js_1.atomicCreateJson)(filePath, entry);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
(0, atomicFs_js_1.atomicWriteJson)(filePath, entry);
|
|
97
|
+
}
|
|
96
98
|
// Update in-memory cache
|
|
97
99
|
this.cache.set(entry.id, entry);
|
|
98
100
|
this.loaded = true;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses Node.js built-in node:sqlite. Zero third-party dependencies.
|
|
5
5
|
*/
|
|
6
|
+
import type { IEmbeddingStore } from './types.js';
|
|
6
7
|
export interface MigrationOptions {
|
|
7
8
|
onProgress?: (current: number, total: number) => void;
|
|
8
9
|
}
|
|
@@ -33,3 +34,15 @@ export declare function migrateJsonToSqlite(jsonDir: string, dbPath: string, opt
|
|
|
33
34
|
* Each entry becomes a separate .json file named by ID.
|
|
34
35
|
*/
|
|
35
36
|
export declare function migrateSqliteToJson(dbPath: string, jsonDir: string, opts?: MigrationOptions): ExportResult;
|
|
37
|
+
export interface EmbeddingMigrationResult {
|
|
38
|
+
migrated: number;
|
|
39
|
+
skipped: number;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Migrate embeddings from a JSON file to an IEmbeddingStore (e.g. SqliteEmbeddingStore).
|
|
44
|
+
*
|
|
45
|
+
* Reads the JSON embedding cache and saves it into the target store.
|
|
46
|
+
* Idempotent: calling again with the same data overwrites safely.
|
|
47
|
+
*/
|
|
48
|
+
export declare function migrateJsonEmbeddingsToStore(jsonEmbeddingPath: string, targetStore: IEmbeddingStore): EmbeddingMigrationResult;
|
|
@@ -10,9 +10,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
11
|
exports.migrateJsonToSqlite = migrateJsonToSqlite;
|
|
12
12
|
exports.migrateSqliteToJson = migrateSqliteToJson;
|
|
13
|
+
exports.migrateJsonEmbeddingsToStore = migrateJsonEmbeddingsToStore;
|
|
13
14
|
const fs_1 = __importDefault(require("fs"));
|
|
14
15
|
const path_1 = __importDefault(require("path"));
|
|
15
16
|
const jsonFileStore_js_1 = require("./jsonFileStore.js");
|
|
17
|
+
const jsonEmbeddingStore_js_1 = require("./jsonEmbeddingStore.js");
|
|
16
18
|
const sqliteStore_js_1 = require("./sqliteStore.js");
|
|
17
19
|
/**
|
|
18
20
|
* Migrate all instructions from JSON files to a SQLite database.
|
|
@@ -91,3 +93,32 @@ function migrateSqliteToJson(dbPath, jsonDir, opts) {
|
|
|
91
93
|
}
|
|
92
94
|
return { exported, errors };
|
|
93
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Migrate embeddings from a JSON file to an IEmbeddingStore (e.g. SqliteEmbeddingStore).
|
|
98
|
+
*
|
|
99
|
+
* Reads the JSON embedding cache and saves it into the target store.
|
|
100
|
+
* Idempotent: calling again with the same data overwrites safely.
|
|
101
|
+
*/
|
|
102
|
+
function migrateJsonEmbeddingsToStore(jsonEmbeddingPath, targetStore) {
|
|
103
|
+
try {
|
|
104
|
+
const jsonStore = new jsonEmbeddingStore_js_1.JsonEmbeddingStore(jsonEmbeddingPath);
|
|
105
|
+
const data = jsonStore.load();
|
|
106
|
+
jsonStore.close();
|
|
107
|
+
if (!data) {
|
|
108
|
+
return { migrated: 0, skipped: 0, error: 'No embedding data in JSON file' };
|
|
109
|
+
}
|
|
110
|
+
const count = Object.keys(data.embeddings).length;
|
|
111
|
+
if (count === 0) {
|
|
112
|
+
return { migrated: 0, skipped: 0 };
|
|
113
|
+
}
|
|
114
|
+
targetStore.save(data);
|
|
115
|
+
return { migrated: count, skipped: 0 };
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return {
|
|
119
|
+
migrated: 0,
|
|
120
|
+
skipped: 0,
|
|
121
|
+
error: err instanceof Error ? err.message : 'Migration failed',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SqliteEmbeddingStore — IEmbeddingStore backed by sqlite-vec (vec0 virtual table).
|
|
3
|
+
*
|
|
4
|
+
* Uses node:sqlite (DatabaseSync) with sqlite-vec extension for KNN vector search.
|
|
5
|
+
* Vectors stored as BLOB (Float32Array buffer), metadata in a companion table.
|
|
6
|
+
*
|
|
7
|
+
* Requires: Node.js ≥22.13.0, sqlite-vec npm package.
|
|
8
|
+
*/
|
|
9
|
+
import type { IEmbeddingStore, EmbeddingCacheData, EmbeddingSearchResult } from './types.js';
|
|
10
|
+
export declare class SqliteEmbeddingStore implements IEmbeddingStore {
|
|
11
|
+
private db;
|
|
12
|
+
private readonly dims;
|
|
13
|
+
private readonly dbPath;
|
|
14
|
+
constructor(dbPath: string, dims?: number);
|
|
15
|
+
private initExtension;
|
|
16
|
+
/**
|
|
17
|
+
* Run PRAGMA integrity_check to detect database corruption.
|
|
18
|
+
* @returns true if the database is intact, false if corrupt.
|
|
19
|
+
*/
|
|
20
|
+
private verifyIntegrity;
|
|
21
|
+
/**
|
|
22
|
+
* Rebuild a corrupt database by closing, deleting, and recreating it.
|
|
23
|
+
*/
|
|
24
|
+
private rebuildDatabase;
|
|
25
|
+
private initSchema;
|
|
26
|
+
load(): EmbeddingCacheData | null;
|
|
27
|
+
save(data: EmbeddingCacheData): void;
|
|
28
|
+
search(queryVector: Float32Array, limit: number): EmbeddingSearchResult[];
|
|
29
|
+
close(): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SqliteEmbeddingStore — IEmbeddingStore backed by sqlite-vec (vec0 virtual table).
|
|
4
|
+
*
|
|
5
|
+
* Uses node:sqlite (DatabaseSync) with sqlite-vec extension for KNN vector search.
|
|
6
|
+
* Vectors stored as BLOB (Float32Array buffer), metadata in a companion table.
|
|
7
|
+
*
|
|
8
|
+
* Requires: Node.js ≥22.13.0, sqlite-vec npm package.
|
|
9
|
+
*/
|
|
10
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.SqliteEmbeddingStore = void 0;
|
|
15
|
+
const node_sqlite_1 = require("node:sqlite");
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const sqlite_vec_1 = require("sqlite-vec");
|
|
19
|
+
const logger_js_1 = require("../logger.js");
|
|
20
|
+
const DEFAULT_VECTOR_DIMS = 384;
|
|
21
|
+
const MAX_VECTOR_DIMS = 65536;
|
|
22
|
+
const EMBEDDING_META_DDL = `CREATE TABLE IF NOT EXISTS embedding_meta (
|
|
23
|
+
instruction_id TEXT PRIMARY KEY,
|
|
24
|
+
model_name TEXT NOT NULL,
|
|
25
|
+
source_hash TEXT NOT NULL,
|
|
26
|
+
index_hash TEXT NOT NULL,
|
|
27
|
+
computed_at TEXT NOT NULL
|
|
28
|
+
)`;
|
|
29
|
+
/** Cache metadata stored in the SQLite metadata table. */
|
|
30
|
+
const META_INDEX_HASH_KEY = 'embedding_index_hash';
|
|
31
|
+
const META_MODEL_NAME_KEY = 'embedding_model_name';
|
|
32
|
+
class SqliteEmbeddingStore {
|
|
33
|
+
db;
|
|
34
|
+
dims;
|
|
35
|
+
dbPath;
|
|
36
|
+
constructor(dbPath, dims = DEFAULT_VECTOR_DIMS) {
|
|
37
|
+
if (!Number.isInteger(dims) || dims < 1 || dims > MAX_VECTOR_DIMS) {
|
|
38
|
+
throw new Error(`Invalid vector dimensions: ${dims}. Must be an integer between 1 and ${MAX_VECTOR_DIMS}.`);
|
|
39
|
+
}
|
|
40
|
+
this.dims = dims;
|
|
41
|
+
this.dbPath = dbPath;
|
|
42
|
+
this.db = new node_sqlite_1.DatabaseSync(dbPath, { allowExtension: true });
|
|
43
|
+
this.initExtension();
|
|
44
|
+
if (!this.verifyIntegrity()) {
|
|
45
|
+
this.rebuildDatabase();
|
|
46
|
+
}
|
|
47
|
+
this.initSchema();
|
|
48
|
+
}
|
|
49
|
+
initExtension() {
|
|
50
|
+
const extPath = (0, sqlite_vec_1.getLoadablePath)();
|
|
51
|
+
// Security: validate the extension path resolves within node_modules
|
|
52
|
+
const resolved = path_1.default.resolve(extPath);
|
|
53
|
+
const nmDir = path_1.default.resolve(__dirname, '..', '..', '..', 'node_modules');
|
|
54
|
+
if (!resolved.startsWith(nmDir + path_1.default.sep) && !resolved.startsWith(nmDir + '/')) {
|
|
55
|
+
throw new Error(`sqlite-vec extension path escapes node_modules: ${resolved}`);
|
|
56
|
+
}
|
|
57
|
+
this.db.loadExtension(extPath);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Run PRAGMA integrity_check to detect database corruption.
|
|
61
|
+
* @returns true if the database is intact, false if corrupt.
|
|
62
|
+
*/
|
|
63
|
+
verifyIntegrity() {
|
|
64
|
+
try {
|
|
65
|
+
const result = this.db.prepare('PRAGMA integrity_check').all();
|
|
66
|
+
if (result.length === 1 && result[0].integrity_check === 'ok')
|
|
67
|
+
return true;
|
|
68
|
+
const issues = result.map(r => r.integrity_check).join('; ');
|
|
69
|
+
(0, logger_js_1.logWarn)(`[embedding-store] Integrity check failed: ${issues}`);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
(0, logger_js_1.logWarn)(`[embedding-store] Integrity check error: ${err instanceof Error ? err.message : 'unknown'}`);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Rebuild a corrupt database by closing, deleting, and recreating it.
|
|
79
|
+
*/
|
|
80
|
+
rebuildDatabase() {
|
|
81
|
+
(0, logger_js_1.logWarn)(`[embedding-store] Rebuilding corrupt database: ${this.dbPath}`);
|
|
82
|
+
try {
|
|
83
|
+
this.db.close();
|
|
84
|
+
}
|
|
85
|
+
catch { /* may already be unusable */ }
|
|
86
|
+
// Remove the corrupt DB and WAL/SHM files
|
|
87
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
88
|
+
try {
|
|
89
|
+
fs_1.default.unlinkSync(this.dbPath + suffix);
|
|
90
|
+
}
|
|
91
|
+
catch { /* file may not exist */ }
|
|
92
|
+
}
|
|
93
|
+
this.db = new node_sqlite_1.DatabaseSync(this.dbPath, { allowExtension: true });
|
|
94
|
+
this.initExtension();
|
|
95
|
+
(0, logger_js_1.logInfo)(`[embedding-store] Database rebuilt successfully`);
|
|
96
|
+
}
|
|
97
|
+
initSchema() {
|
|
98
|
+
this.db.exec('PRAGMA journal_mode = WAL');
|
|
99
|
+
this.db.exec('PRAGMA busy_timeout = 5000');
|
|
100
|
+
// Ensure metadata table exists (may already exist from SqliteStore)
|
|
101
|
+
this.db.exec('CREATE TABLE IF NOT EXISTS metadata (key TEXT PRIMARY KEY, value TEXT)');
|
|
102
|
+
this.db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS embeddings USING vec0(
|
|
103
|
+
instruction_id TEXT PRIMARY KEY,
|
|
104
|
+
embedding float[${this.dims}]
|
|
105
|
+
)`);
|
|
106
|
+
this.db.exec(EMBEDDING_META_DDL);
|
|
107
|
+
}
|
|
108
|
+
load() {
|
|
109
|
+
// Read cache metadata
|
|
110
|
+
const hashRow = this.db.prepare('SELECT value FROM metadata WHERE key = ?').get(META_INDEX_HASH_KEY);
|
|
111
|
+
const modelRow = this.db.prepare('SELECT value FROM metadata WHERE key = ?').get(META_MODEL_NAME_KEY);
|
|
112
|
+
if (!hashRow)
|
|
113
|
+
return null;
|
|
114
|
+
// Read all embeddings
|
|
115
|
+
const rows = this.db.prepare('SELECT instruction_id, embedding FROM embeddings').all();
|
|
116
|
+
if (rows.length === 0) {
|
|
117
|
+
// CodeQL flagged the original `&& !hashRow` as dead code (already
|
|
118
|
+
// null-guarded at L115). We cannot upgrade to `if (rows.length === 0) return null`
|
|
119
|
+
// because the public contract (see embeddingStore.contract.spec.ts
|
|
120
|
+
// "save() with empty embeddings map succeeds") requires a non-null cache
|
|
121
|
+
// when hashRow exists but no embeddings have been persisted. Fall through
|
|
122
|
+
// to return { indexHash, entryHashes, embeddings: {} } so subsequent saves
|
|
123
|
+
// can layer onto the same metadata.
|
|
124
|
+
}
|
|
125
|
+
const embeddings = {};
|
|
126
|
+
for (const row of rows) {
|
|
127
|
+
// Validate buffer bounds before creating Float32Array view
|
|
128
|
+
const expectedBytes = this.dims * 4;
|
|
129
|
+
if (row.embedding.byteLength !== expectedBytes) {
|
|
130
|
+
(0, logger_js_1.logWarn)(`[embedding-store] Skipping entry '${row.instruction_id}': buffer size ${row.embedding.byteLength} bytes, expected ${expectedBytes} (${this.dims} dims × 4 bytes)`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
embeddings[row.instruction_id] = Array.from(new Float32Array(row.embedding.buffer, row.embedding.byteOffset, this.dims));
|
|
134
|
+
}
|
|
135
|
+
// Read entry hashes from embedding_meta
|
|
136
|
+
const metaRows = this.db.prepare('SELECT instruction_id, source_hash FROM embedding_meta').all();
|
|
137
|
+
const entryHashes = {};
|
|
138
|
+
for (const m of metaRows) {
|
|
139
|
+
entryHashes[m.instruction_id] = m.source_hash;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
indexHash: hashRow.value,
|
|
143
|
+
modelName: modelRow?.value,
|
|
144
|
+
entryHashes,
|
|
145
|
+
embeddings,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
save(data) {
|
|
149
|
+
// TODO: Replace full-table clear+reinsert with incremental upsert for large datasets.
|
|
150
|
+
// Use a diff of entryHashes to INSERT OR REPLACE only changed entries and DELETE removed ones.
|
|
151
|
+
// Use a transaction for atomicity
|
|
152
|
+
const tx = this.db.prepare('BEGIN');
|
|
153
|
+
const commit = this.db.prepare('COMMIT');
|
|
154
|
+
const rollback = this.db.prepare('ROLLBACK');
|
|
155
|
+
try {
|
|
156
|
+
tx.run();
|
|
157
|
+
// Clear existing data
|
|
158
|
+
this.db.exec('DELETE FROM embeddings');
|
|
159
|
+
this.db.exec('DELETE FROM embedding_meta');
|
|
160
|
+
// Upsert cache metadata
|
|
161
|
+
const upsertMeta = this.db.prepare('INSERT OR REPLACE INTO metadata (key, value) VALUES (?, ?)');
|
|
162
|
+
upsertMeta.run(META_INDEX_HASH_KEY, data.indexHash);
|
|
163
|
+
if (data.modelName) {
|
|
164
|
+
upsertMeta.run(META_MODEL_NAME_KEY, data.modelName);
|
|
165
|
+
}
|
|
166
|
+
// Insert embeddings
|
|
167
|
+
const insertVec = this.db.prepare('INSERT INTO embeddings (instruction_id, embedding) VALUES (?, ?)');
|
|
168
|
+
const insertMeta = this.db.prepare('INSERT INTO embedding_meta (instruction_id, model_name, source_hash, index_hash, computed_at) VALUES (?, ?, ?, ?, ?)');
|
|
169
|
+
const now = new Date().toISOString();
|
|
170
|
+
for (const [id, vec] of Object.entries(data.embeddings)) {
|
|
171
|
+
if (vec.length !== this.dims) {
|
|
172
|
+
throw new Error(`Vector dimension mismatch for '${id}': expected ${this.dims}, got ${vec.length}`);
|
|
173
|
+
}
|
|
174
|
+
for (let i = 0; i < vec.length; i++) {
|
|
175
|
+
if (!Number.isFinite(vec[i])) {
|
|
176
|
+
throw new Error(`Invalid vector value for '${id}' at index ${i}: ${vec[i]}. Vectors must contain only finite numbers.`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const buf = Buffer.from(new Float32Array(vec).buffer);
|
|
180
|
+
insertVec.run(id, buf);
|
|
181
|
+
insertMeta.run(id, data.modelName ?? 'unknown', data.entryHashes?.[id] ?? '', data.indexHash, now);
|
|
182
|
+
}
|
|
183
|
+
commit.run();
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
try {
|
|
187
|
+
rollback.run();
|
|
188
|
+
}
|
|
189
|
+
catch { /* rollback may fail if tx already aborted */ }
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
search(queryVector, limit) {
|
|
194
|
+
if (limit <= 0)
|
|
195
|
+
return [];
|
|
196
|
+
if (queryVector.length !== this.dims) {
|
|
197
|
+
(0, logger_js_1.logWarn)(`[embedding-store] Search query dimension mismatch: expected ${this.dims}, got ${queryVector.length}`);
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const buf = Buffer.from(queryVector.buffer, queryVector.byteOffset, queryVector.byteLength);
|
|
202
|
+
const rows = this.db.prepare('SELECT instruction_id, distance FROM embeddings WHERE embedding MATCH ? ORDER BY distance LIMIT ?').all(buf, limit);
|
|
203
|
+
return rows.map(r => ({
|
|
204
|
+
id: r.instruction_id,
|
|
205
|
+
distance: r.distance,
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
(0, logger_js_1.logWarn)(`[embedding-store] search failed: ${err instanceof Error ? err.message : 'unknown'}`);
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
close() {
|
|
214
|
+
try {
|
|
215
|
+
this.db.close();
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Already closed
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
exports.SqliteEmbeddingStore = SqliteEmbeddingStore;
|
|
@@ -23,7 +23,9 @@ export declare class SqliteStore implements IInstructionStore {
|
|
|
23
23
|
load(): LoadResult;
|
|
24
24
|
close(): void;
|
|
25
25
|
get(id: string): InstructionEntry | null;
|
|
26
|
-
write(entry: InstructionEntry
|
|
26
|
+
write(entry: InstructionEntry, opts?: {
|
|
27
|
+
createOnly?: boolean;
|
|
28
|
+
}): void;
|
|
27
29
|
remove(id: string): void;
|
|
28
30
|
list(opts?: ListOptions): InstructionEntry[];
|
|
29
31
|
query(opts: QueryOptions): InstructionEntry[];
|
|
@@ -162,8 +162,8 @@ class SqliteStore {
|
|
|
162
162
|
this.ensureLoaded();
|
|
163
163
|
return this.cache.get(id) ?? null;
|
|
164
164
|
}
|
|
165
|
-
write(entry) {
|
|
166
|
-
const sql =
|
|
165
|
+
write(entry, opts) {
|
|
166
|
+
const sql = `${opts?.createOnly ? 'INSERT INTO' : 'INSERT OR REPLACE INTO'} instructions (
|
|
167
167
|
id, title, body, rationale, priority, audience, requirement,
|
|
168
168
|
categories, content_type, primary_category, source_hash,
|
|
169
169
|
schema_version, deprecated_by, created_at, updated_at,
|
|
@@ -123,7 +123,9 @@ export interface IInstructionStore {
|
|
|
123
123
|
* Write (create or update) an instruction.
|
|
124
124
|
* @param entry - The instruction to persist.
|
|
125
125
|
*/
|
|
126
|
-
write(entry: InstructionEntry
|
|
126
|
+
write(entry: InstructionEntry, opts?: {
|
|
127
|
+
createOnly?: boolean;
|
|
128
|
+
}): void;
|
|
127
129
|
/**
|
|
128
130
|
* Remove an instruction by ID. No-op if not found.
|
|
129
131
|
* @param id - Instruction ID to remove.
|
|
@@ -169,3 +171,48 @@ export interface IInstructionStore {
|
|
|
169
171
|
*/
|
|
170
172
|
count(): number;
|
|
171
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Persisted embedding cache format (backwards-compatible: entryHashes is optional).
|
|
176
|
+
*/
|
|
177
|
+
export interface EmbeddingCacheData {
|
|
178
|
+
indexHash: string;
|
|
179
|
+
modelName?: string;
|
|
180
|
+
/** Per-entry content hashes for incremental invalidation (required for v2+). */
|
|
181
|
+
entryHashes?: Record<string, string>;
|
|
182
|
+
embeddings: Record<string, number[]>;
|
|
183
|
+
}
|
|
184
|
+
/** A single embedding search result with distance score. */
|
|
185
|
+
export interface EmbeddingSearchResult {
|
|
186
|
+
id: string;
|
|
187
|
+
distance: number;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Storage backend interface for embedding vectors.
|
|
191
|
+
*
|
|
192
|
+
* Implementations:
|
|
193
|
+
* - JsonEmbeddingStore: Flat JSON file (default, current behavior)
|
|
194
|
+
* - SqliteEmbeddingStore: sqlite-vec vec0 virtual table
|
|
195
|
+
*/
|
|
196
|
+
export interface IEmbeddingStore {
|
|
197
|
+
/**
|
|
198
|
+
* Load cached embedding data from the backing store.
|
|
199
|
+
* @returns The cached data, or null if no data exists.
|
|
200
|
+
*/
|
|
201
|
+
load(): EmbeddingCacheData | null;
|
|
202
|
+
/**
|
|
203
|
+
* Save embedding data to the backing store.
|
|
204
|
+
* @param data - The embedding cache data to persist.
|
|
205
|
+
*/
|
|
206
|
+
save(data: EmbeddingCacheData): void;
|
|
207
|
+
/**
|
|
208
|
+
* Search for the nearest vectors to a query vector (KNN).
|
|
209
|
+
* @param queryVector - The query embedding vector.
|
|
210
|
+
* @param limit - Maximum number of results to return.
|
|
211
|
+
* @returns Array of { id, distance } sorted by distance ascending.
|
|
212
|
+
*/
|
|
213
|
+
search(queryVector: Float32Array, limit: number): EmbeddingSearchResult[];
|
|
214
|
+
/**
|
|
215
|
+
* Close the store and release resources.
|
|
216
|
+
*/
|
|
217
|
+
close(): void;
|
|
218
|
+
}
|