@gzoo/cortex 0.5.11 → 0.5.13
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/cortex.mjs +358 -159
- package/package.json +1 -1
- package/packages/cli/dist/commands/config.d.ts.map +1 -1
- package/packages/cli/dist/commands/config.js +9 -1
- package/packages/cli/dist/commands/config.js.map +1 -1
- package/packages/cli/dist/commands/ingest.js +3 -1
- package/packages/cli/dist/commands/ingest.js.map +1 -1
- package/packages/cli/dist/commands/mcp.js +1 -1
- package/packages/cli/dist/commands/mcp.js.map +1 -1
- package/packages/cli/dist/commands/models.js +2 -1
- package/packages/cli/dist/commands/models.js.map +1 -1
- package/packages/cli/dist/commands/serve.js +1 -1
- package/packages/cli/dist/commands/serve.js.map +1 -1
- package/packages/cli/dist/commands/status.d.ts.map +1 -1
- package/packages/cli/dist/commands/status.js +15 -4
- package/packages/cli/dist/commands/status.js.map +1 -1
- package/packages/cli/dist/commands/watch.js +1 -0
- package/packages/cli/dist/commands/watch.js.map +1 -1
- package/packages/cli/dist/index.js +1 -1
- package/packages/cli/dist/index.js.map +1 -1
- package/packages/graph/dist/migrations/002-add-indexes.d.ts +4 -0
- package/packages/graph/dist/migrations/002-add-indexes.d.ts.map +1 -0
- package/packages/graph/dist/migrations/002-add-indexes.js +30 -0
- package/packages/graph/dist/migrations/002-add-indexes.js.map +1 -0
- package/packages/graph/dist/query-engine.d.ts.map +1 -1
- package/packages/graph/dist/query-engine.js +16 -25
- package/packages/graph/dist/query-engine.js.map +1 -1
- package/packages/graph/dist/sqlite-store.d.ts +5 -1
- package/packages/graph/dist/sqlite-store.d.ts.map +1 -1
- package/packages/graph/dist/sqlite-store.js +152 -118
- package/packages/graph/dist/sqlite-store.js.map +1 -1
- package/packages/ingest/dist/parsers/conversation.js +2 -2
- package/packages/ingest/dist/parsers/conversation.js.map +1 -1
- package/packages/ingest/dist/pipeline.d.ts +7 -0
- package/packages/ingest/dist/pipeline.d.ts.map +1 -1
- package/packages/ingest/dist/pipeline.js +43 -5
- package/packages/ingest/dist/pipeline.js.map +1 -1
- package/packages/ingest/dist/watcher.d.ts +1 -0
- package/packages/ingest/dist/watcher.d.ts.map +1 -1
- package/packages/ingest/dist/watcher.js +22 -11
- package/packages/ingest/dist/watcher.js.map +1 -1
- package/packages/llm/dist/cache.js +5 -5
- package/packages/llm/dist/cache.js.map +1 -1
- package/packages/llm/dist/providers/ollama.d.ts +1 -0
- package/packages/llm/dist/providers/ollama.d.ts.map +1 -1
- package/packages/llm/dist/providers/ollama.js +68 -17
- package/packages/llm/dist/providers/ollama.js.map +1 -1
- package/packages/llm/dist/providers/openai-compatible.d.ts.map +1 -1
- package/packages/llm/dist/providers/openai-compatible.js +7 -1
- package/packages/llm/dist/providers/openai-compatible.js.map +1 -1
- package/packages/llm/dist/token-tracker.d.ts +1 -0
- package/packages/llm/dist/token-tracker.d.ts.map +1 -1
- package/packages/llm/dist/token-tracker.js +19 -0
- package/packages/llm/dist/token-tracker.js.map +1 -1
- package/packages/server/dist/index.d.ts.map +1 -1
- package/packages/server/dist/index.js +15 -2
- package/packages/server/dist/index.js.map +1 -1
- package/packages/server/dist/middleware/auth.d.ts.map +1 -1
- package/packages/server/dist/middleware/auth.js +4 -0
- package/packages/server/dist/middleware/auth.js.map +1 -1
- package/packages/server/dist/routes/status.js +1 -1
- package/packages/server/dist/routes/status.js.map +1 -1
|
@@ -4,6 +4,17 @@ const AVG_CHARS_PER_TOKEN = 4;
|
|
|
4
4
|
function estimateTokens(text) {
|
|
5
5
|
return Math.ceil(text.length / AVG_CHARS_PER_TOKEN);
|
|
6
6
|
}
|
|
7
|
+
const FTS_STOP_WORDS = new Set([
|
|
8
|
+
'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
9
|
+
'of', 'with', 'by', 'from', 'is', 'are', 'was', 'were', 'be', 'been',
|
|
10
|
+
'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
|
11
|
+
'could', 'should', 'may', 'might', 'shall', 'can', 'need', 'must',
|
|
12
|
+
'what', 'which', 'who', 'how', 'why', 'when', 'where', 'that', 'this',
|
|
13
|
+
'these', 'those', 'it', 'its', 'me', 'my', 'you', 'your', 'we', 'our',
|
|
14
|
+
'they', 'their', 'he', 'she', 'i', 'all', 'any', 'each', 'some', 'no',
|
|
15
|
+
'not', 'so', 'yet', 'use', 'used', 'using', 'about', 'tell', 'know',
|
|
16
|
+
'get', 'got', 'make', 'made', 'see', 'give', 'go', 'come', 'take',
|
|
17
|
+
]);
|
|
7
18
|
export class QueryEngine {
|
|
8
19
|
sqliteStore;
|
|
9
20
|
vectorStore;
|
|
@@ -42,19 +53,10 @@ export class QueryEngine {
|
|
|
42
53
|
}
|
|
43
54
|
// Filter entities by project privacy level
|
|
44
55
|
const privacyFiltered = await this.filterByPrivacy(contextEntities);
|
|
45
|
-
// Gather relationships between context entities
|
|
56
|
+
// Gather relationships between context entities (single batch query)
|
|
46
57
|
const entityIds = new Set(privacyFiltered.map((e) => e.id));
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const rels = await this.sqliteStore.getRelationshipsForEntity(entity.id);
|
|
50
|
-
for (const rel of rels) {
|
|
51
|
-
if (entityIds.has(rel.sourceEntityId) && entityIds.has(rel.targetEntityId)) {
|
|
52
|
-
relationships.push(rel);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
// Deduplicate relationships
|
|
57
|
-
const uniqueRels = [...new Map(relationships.map((r) => [r.id, r])).values()];
|
|
58
|
+
const allRels = await this.sqliteStore.getRelationshipsForEntities([...entityIds]);
|
|
59
|
+
const uniqueRels = allRels.filter((r) => entityIds.has(r.sourceEntityId) && entityIds.has(r.targetEntityId));
|
|
58
60
|
const relTokens = uniqueRels.reduce((sum, r) => sum + estimateTokens(r.description ?? '') + 20, 0);
|
|
59
61
|
// Recalculate tokens after privacy filtering (content may have been redacted)
|
|
60
62
|
const filteredTokens = privacyFiltered.reduce((sum, e) => sum + estimateTokens(e.content) + estimateTokens(e.name), 0);
|
|
@@ -110,22 +112,11 @@ export class QueryEngine {
|
|
|
110
112
|
* entities matching ANY meaningful keyword are returned.
|
|
111
113
|
*/
|
|
112
114
|
buildFtsQuery(query) {
|
|
113
|
-
const stopWords = new Set([
|
|
114
|
-
'a', 'an', 'the', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
115
|
-
'of', 'with', 'by', 'from', 'is', 'are', 'was', 'were', 'be', 'been',
|
|
116
|
-
'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
|
117
|
-
'could', 'should', 'may', 'might', 'shall', 'can', 'need', 'must',
|
|
118
|
-
'what', 'which', 'who', 'how', 'why', 'when', 'where', 'that', 'this',
|
|
119
|
-
'these', 'those', 'it', 'its', 'me', 'my', 'you', 'your', 'we', 'our',
|
|
120
|
-
'they', 'their', 'he', 'she', 'i', 'all', 'any', 'each', 'some', 'no',
|
|
121
|
-
'not', 'so', 'yet', 'use', 'used', 'using', 'about', 'tell', 'know',
|
|
122
|
-
'get', 'got', 'make', 'made', 'see', 'give', 'go', 'come', 'take',
|
|
123
|
-
]);
|
|
124
115
|
const keywords = query
|
|
125
|
-
.replace(/[^a-zA-Z0-9\s]/g, ' ')
|
|
116
|
+
.replace(/[^a-zA-Z0-9\s]/g, ' ')
|
|
126
117
|
.toLowerCase()
|
|
127
118
|
.split(/\s+/)
|
|
128
|
-
.filter((w) => w.length >= 3 && !
|
|
119
|
+
.filter((w) => w.length >= 3 && !FTS_STOP_WORDS.has(w));
|
|
129
120
|
if (keywords.length === 0) {
|
|
130
121
|
// Fallback: sanitize raw query for FTS5 safety
|
|
131
122
|
return query.replace(/[^a-zA-Z0-9\s]/g, ' ').trim();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-engine.js","sourceRoot":"","sources":["../src/query-engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAI5C,MAAM,MAAM,GAAG,YAAY,CAAC,oBAAoB,CAAC,CAAC;AAelD,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,mBAAmB,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,OAAO,WAAW;IACd,WAAW,CAAc;IACzB,WAAW,CAAc;IACzB,gBAAgB,CAAS;IACzB,iBAAiB,CAAS;IAC1B,SAAS,CAAS;IAClB,YAAY,CAAS;IAE7B,YACE,WAAwB,EACxB,WAAwB,EACxB,UAA8B,EAAE;QAEhC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,MAAM,CAAC;QAC3D,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,IAAI,EAAE,CAAC;QACzD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,GAAG,CAAC;QAC1C,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,KAAa,EACb,cAA6B,EAC7B,SAAkB;QAElB,wCAAwC;QACxC,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACpD,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC;YAChC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;SACnF,CAAC,CAAC;QAEH,yBAAyB;QACzB,MAAM,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;QAEpE,6DAA6D;QAC7D,MAAM,eAAe,GAAa,EAAE,CAAC;QACrC,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAC,CAAC;QAElE,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;YACpC,IAAI,eAAe,CAAC,MAAM,IAAI,IAAI,CAAC,iBAAiB;gBAAE,MAAM;YAC5D,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAClF,IAAI,WAAW,GAAG,YAAY,GAAG,iBAAiB;gBAAE,MAAM;YAC1D,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC7B,WAAW,IAAI,YAAY,CAAC;QAC9B,CAAC;QAED,2CAA2C;QAC3C,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC;QAEpE,
|
|
1
|
+
{"version":3,"file":"query-engine.js","sourceRoot":"","sources":["../src/query-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -60,14 +60,18 @@ export declare class SQLiteStore implements GraphStore {
|
|
|
60
60
|
private migrate;
|
|
61
61
|
private backupSync;
|
|
62
62
|
close(): void;
|
|
63
|
+
transaction<T>(fn: () => T): T;
|
|
64
|
+
createEntitySync(entity: Omit<Entity, 'id' | 'createdAt' | 'updatedAt'>): Entity;
|
|
63
65
|
createEntity(entity: Omit<Entity, 'id' | 'createdAt' | 'updatedAt'>): Promise<Entity>;
|
|
66
|
+
private _createEntity;
|
|
64
67
|
getEntity(id: string): Promise<Entity | null>;
|
|
65
68
|
updateEntity(id: string, updates: Partial<Entity>): Promise<Entity>;
|
|
66
69
|
deleteEntity(id: string, soft?: boolean): Promise<void>;
|
|
67
70
|
findEntities(query: EntityQuery): Promise<Entity[]>;
|
|
68
71
|
createRelationship(rel: Omit<Relationship, 'id' | 'createdAt' | 'updatedAt'>): Promise<Relationship>;
|
|
69
72
|
getRelationship(id: string): Promise<Relationship | null>;
|
|
70
|
-
getRelationshipsForEntity(entityId: string, direction?: 'in' | 'out' | 'both'): Promise<Relationship[]>;
|
|
73
|
+
getRelationshipsForEntity(entityId: string, direction?: 'in' | 'out' | 'both', limit?: number): Promise<Relationship[]>;
|
|
74
|
+
getRelationshipsForEntities(entityIds: string[]): Promise<Relationship[]>;
|
|
71
75
|
deleteRelationship(id: string): Promise<void>;
|
|
72
76
|
deleteBySourcePath(pathPrefix: string): {
|
|
73
77
|
deletedEntities: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../src/sqlite-store.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,aAAa,EACb,UAAU,EACV,OAAO,EACP,WAAW,EACX,UAAU,EACV,eAAe,EACf,UAAU,EACX,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../src/sqlite-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ import { dirname } from 'node:path';
|
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { CortexError, GRAPH_DB_ERROR, GRAPH_ENTITY_NOT_FOUND, } from '@cortex/core';
|
|
7
7
|
import { up as applyInitialMigration } from './migrations/001-initial.js';
|
|
8
|
+
import { up as applyIndexMigration } from './migrations/002-add-indexes.js';
|
|
8
9
|
function resolveHomePath(p) {
|
|
9
10
|
return p.startsWith('~') ? p.replace('~', homedir()) : p;
|
|
10
11
|
}
|
|
@@ -93,11 +94,16 @@ export class SQLiteStore {
|
|
|
93
94
|
constructor(options = {}) {
|
|
94
95
|
const { dbPath = '~/.cortex/cortex.db', walMode = true, backupOnStartup = true, } = options;
|
|
95
96
|
this.dbPath = resolveHomePath(dbPath);
|
|
96
|
-
mkdirSync(dirname(this.dbPath), { recursive: true });
|
|
97
|
+
mkdirSync(dirname(this.dbPath), { recursive: true, mode: 0o700 });
|
|
97
98
|
if (backupOnStartup) {
|
|
98
99
|
this.backupSync();
|
|
99
100
|
}
|
|
100
101
|
this.db = new Database(this.dbPath);
|
|
102
|
+
// Restrict DB file permissions (owner-only read/write)
|
|
103
|
+
try {
|
|
104
|
+
chmodSync(this.dbPath, 0o600);
|
|
105
|
+
}
|
|
106
|
+
catch { /* Windows — ignore */ }
|
|
101
107
|
if (walMode) {
|
|
102
108
|
this.db.pragma('journal_mode = WAL');
|
|
103
109
|
}
|
|
@@ -108,6 +114,7 @@ export class SQLiteStore {
|
|
|
108
114
|
migrate() {
|
|
109
115
|
try {
|
|
110
116
|
applyInitialMigration(this.db);
|
|
117
|
+
applyIndexMigration(this.db);
|
|
111
118
|
}
|
|
112
119
|
catch (err) {
|
|
113
120
|
throw new CortexError(GRAPH_DB_ERROR, 'critical', 'graph', `Migration failed: ${err instanceof Error ? err.message : String(err)}`, undefined, 'Delete the database and restart.');
|
|
@@ -132,28 +139,37 @@ export class SQLiteStore {
|
|
|
132
139
|
close() {
|
|
133
140
|
this.db.close();
|
|
134
141
|
}
|
|
142
|
+
transaction(fn) {
|
|
143
|
+
return this.db.transaction(fn)();
|
|
144
|
+
}
|
|
135
145
|
// --- Entities ---
|
|
146
|
+
createEntitySync(entity) {
|
|
147
|
+
return this._createEntity(entity);
|
|
148
|
+
}
|
|
136
149
|
async createEntity(entity) {
|
|
150
|
+
return this._createEntity(entity);
|
|
151
|
+
}
|
|
152
|
+
_createEntity(entity) {
|
|
137
153
|
const id = randomUUID();
|
|
138
154
|
const ts = now();
|
|
139
|
-
this.db.prepare(`
|
|
140
|
-
INSERT INTO entities (
|
|
141
|
-
id, type, name, content, summary, properties, confidence,
|
|
142
|
-
source_file, source_start_line, source_end_line,
|
|
143
|
-
project_id, extracted_by, tags, status, created_at, updated_at
|
|
144
|
-
) VALUES (
|
|
145
|
-
?, ?, ?, ?, ?, ?, ?,
|
|
146
|
-
?, ?, ?,
|
|
147
|
-
?, ?, ?, ?, ?, ?
|
|
148
|
-
)
|
|
155
|
+
this.db.prepare(`
|
|
156
|
+
INSERT INTO entities (
|
|
157
|
+
id, type, name, content, summary, properties, confidence,
|
|
158
|
+
source_file, source_start_line, source_end_line,
|
|
159
|
+
project_id, extracted_by, tags, status, created_at, updated_at
|
|
160
|
+
) VALUES (
|
|
161
|
+
?, ?, ?, ?, ?, ?, ?,
|
|
162
|
+
?, ?, ?,
|
|
163
|
+
?, ?, ?, ?, ?, ?
|
|
164
|
+
)
|
|
149
165
|
`).run(id, entity.type, entity.name, entity.content, entity.summary ?? null, JSON.stringify(entity.properties), entity.confidence, entity.sourceFile, entity.sourceRange?.startLine ?? null, entity.sourceRange?.endLine ?? null, entity.projectId, JSON.stringify(entity.extractedBy), JSON.stringify(entity.tags), entity.status, ts, ts);
|
|
150
166
|
// Sync to FTS
|
|
151
|
-
this.db.prepare(`
|
|
152
|
-
INSERT INTO entities_fts (rowid, name, content, summary, tags)
|
|
153
|
-
VALUES (
|
|
154
|
-
(SELECT rowid FROM entities WHERE id = ?),
|
|
155
|
-
?, ?, ?, ?
|
|
156
|
-
)
|
|
167
|
+
this.db.prepare(`
|
|
168
|
+
INSERT INTO entities_fts (rowid, name, content, summary, tags)
|
|
169
|
+
VALUES (
|
|
170
|
+
(SELECT rowid FROM entities WHERE id = ?),
|
|
171
|
+
?, ?, ?, ?
|
|
172
|
+
)
|
|
157
173
|
`).run(id, entity.name, entity.content, entity.summary ?? '', entity.tags.join(' '));
|
|
158
174
|
return { ...entity, id, createdAt: ts, updatedAt: ts };
|
|
159
175
|
}
|
|
@@ -167,18 +183,18 @@ export class SQLiteStore {
|
|
|
167
183
|
throw new CortexError(GRAPH_ENTITY_NOT_FOUND, 'low', 'graph', `Entity not found: ${id}`, { entityId: id });
|
|
168
184
|
}
|
|
169
185
|
const merged = { ...existing, ...updates, updatedAt: now() };
|
|
170
|
-
this.db.prepare(`
|
|
171
|
-
UPDATE entities SET
|
|
172
|
-
type = ?, name = ?, content = ?, summary = ?,
|
|
173
|
-
properties = ?, confidence = ?,
|
|
174
|
-
source_file = ?, source_start_line = ?, source_end_line = ?,
|
|
175
|
-
extracted_by = ?, tags = ?, status = ?, updated_at = ?
|
|
176
|
-
WHERE id = ?
|
|
186
|
+
this.db.prepare(`
|
|
187
|
+
UPDATE entities SET
|
|
188
|
+
type = ?, name = ?, content = ?, summary = ?,
|
|
189
|
+
properties = ?, confidence = ?,
|
|
190
|
+
source_file = ?, source_start_line = ?, source_end_line = ?,
|
|
191
|
+
extracted_by = ?, tags = ?, status = ?, updated_at = ?
|
|
192
|
+
WHERE id = ?
|
|
177
193
|
`).run(merged.type, merged.name, merged.content, merged.summary ?? null, JSON.stringify(merged.properties), merged.confidence, merged.sourceFile, merged.sourceRange?.startLine ?? null, merged.sourceRange?.endLine ?? null, JSON.stringify(merged.extractedBy), JSON.stringify(merged.tags), merged.status, merged.updatedAt, id);
|
|
178
194
|
// Re-sync FTS
|
|
179
|
-
this.db.prepare(`
|
|
180
|
-
UPDATE entities_fts SET name = ?, content = ?, summary = ?, tags = ?
|
|
181
|
-
WHERE rowid = (SELECT rowid FROM entities WHERE id = ?)
|
|
195
|
+
this.db.prepare(`
|
|
196
|
+
UPDATE entities_fts SET name = ?, content = ?, summary = ?, tags = ?
|
|
197
|
+
WHERE rowid = (SELECT rowid FROM entities WHERE id = ?)
|
|
182
198
|
`).run(merged.name, merged.content, merged.summary ?? '', merged.tags.join(' '), id);
|
|
183
199
|
return merged;
|
|
184
200
|
}
|
|
@@ -217,20 +233,25 @@ export class SQLiteStore {
|
|
|
217
233
|
}
|
|
218
234
|
let sql;
|
|
219
235
|
if (query.search) {
|
|
236
|
+
// Sanitize FTS input: strip operators to prevent FTS5 injection
|
|
237
|
+
const sanitizedSearch = query.search.replace(/[^a-zA-Z0-9\s]/g, ' ').trim();
|
|
238
|
+
if (!sanitizedSearch) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
220
241
|
// Use FTS for text search
|
|
221
|
-
sql = `
|
|
222
|
-
SELECT e.* FROM entities e
|
|
223
|
-
JOIN entities_fts fts ON fts.rowid = e.rowid
|
|
224
|
-
WHERE fts.entities_fts MATCH ? AND ${conditions.join(' AND ')}
|
|
225
|
-
ORDER BY rank
|
|
242
|
+
sql = `
|
|
243
|
+
SELECT e.* FROM entities e
|
|
244
|
+
JOIN entities_fts fts ON fts.rowid = e.rowid
|
|
245
|
+
WHERE fts.entities_fts MATCH ? AND ${conditions.join(' AND ')}
|
|
246
|
+
ORDER BY rank
|
|
226
247
|
`;
|
|
227
|
-
params.unshift(
|
|
248
|
+
params.unshift(sanitizedSearch);
|
|
228
249
|
}
|
|
229
250
|
else {
|
|
230
|
-
sql = `
|
|
231
|
-
SELECT * FROM entities
|
|
232
|
-
WHERE ${conditions.join(' AND ')}
|
|
233
|
-
ORDER BY created_at DESC
|
|
251
|
+
sql = `
|
|
252
|
+
SELECT * FROM entities
|
|
253
|
+
WHERE ${conditions.join(' AND ')}
|
|
254
|
+
ORDER BY created_at DESC
|
|
234
255
|
`;
|
|
235
256
|
}
|
|
236
257
|
if (query.limit) {
|
|
@@ -248,12 +269,12 @@ export class SQLiteStore {
|
|
|
248
269
|
async createRelationship(rel) {
|
|
249
270
|
const id = randomUUID();
|
|
250
271
|
const ts = now();
|
|
251
|
-
this.db.prepare(`
|
|
252
|
-
INSERT INTO relationships (
|
|
253
|
-
id, type, source_entity_id, target_entity_id,
|
|
254
|
-
description, confidence, properties, extracted_by,
|
|
255
|
-
created_at, updated_at
|
|
256
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
272
|
+
this.db.prepare(`
|
|
273
|
+
INSERT INTO relationships (
|
|
274
|
+
id, type, source_entity_id, target_entity_id,
|
|
275
|
+
description, confidence, properties, extracted_by,
|
|
276
|
+
created_at, updated_at
|
|
277
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
257
278
|
`).run(id, rel.type, rel.sourceEntityId, rel.targetEntityId, rel.description ?? null, rel.confidence, JSON.stringify(rel.properties), JSON.stringify(rel.extractedBy), ts, ts);
|
|
258
279
|
return { ...rel, id, createdAt: ts, updatedAt: ts };
|
|
259
280
|
}
|
|
@@ -261,24 +282,37 @@ export class SQLiteStore {
|
|
|
261
282
|
const row = this.db.prepare('SELECT * FROM relationships WHERE id = ?').get(id);
|
|
262
283
|
return row ? rowToRelationship(row) : null;
|
|
263
284
|
}
|
|
264
|
-
async getRelationshipsForEntity(entityId, direction = 'both') {
|
|
285
|
+
async getRelationshipsForEntity(entityId, direction = 'both', limit = 200) {
|
|
265
286
|
let sql;
|
|
266
287
|
let params;
|
|
267
288
|
if (direction === 'out') {
|
|
268
|
-
sql = 'SELECT * FROM relationships WHERE source_entity_id = ?';
|
|
269
|
-
params = [entityId];
|
|
289
|
+
sql = 'SELECT * FROM relationships WHERE source_entity_id = ? LIMIT ?';
|
|
290
|
+
params = [entityId, limit];
|
|
270
291
|
}
|
|
271
292
|
else if (direction === 'in') {
|
|
272
|
-
sql = 'SELECT * FROM relationships WHERE target_entity_id = ?';
|
|
273
|
-
params = [entityId];
|
|
293
|
+
sql = 'SELECT * FROM relationships WHERE target_entity_id = ? LIMIT ?';
|
|
294
|
+
params = [entityId, limit];
|
|
274
295
|
}
|
|
275
296
|
else {
|
|
276
|
-
sql = 'SELECT * FROM relationships WHERE source_entity_id = ? OR target_entity_id = ?';
|
|
277
|
-
params = [entityId, entityId];
|
|
297
|
+
sql = 'SELECT * FROM relationships WHERE source_entity_id = ? OR target_entity_id = ? LIMIT ?';
|
|
298
|
+
params = [entityId, entityId, limit];
|
|
278
299
|
}
|
|
279
300
|
const rows = this.db.prepare(sql).all(...params);
|
|
280
301
|
return rows.map(rowToRelationship);
|
|
281
302
|
}
|
|
303
|
+
async getRelationshipsForEntities(entityIds) {
|
|
304
|
+
if (entityIds.length === 0)
|
|
305
|
+
return [];
|
|
306
|
+
const placeholders = entityIds.map(() => '?').join(',');
|
|
307
|
+
const sql = `
|
|
308
|
+
SELECT * FROM relationships
|
|
309
|
+
WHERE source_entity_id IN (${placeholders})
|
|
310
|
+
OR target_entity_id IN (${placeholders})
|
|
311
|
+
LIMIT 2000
|
|
312
|
+
`;
|
|
313
|
+
const rows = this.db.prepare(sql).all(...entityIds, ...entityIds);
|
|
314
|
+
return rows.map(rowToRelationship);
|
|
315
|
+
}
|
|
282
316
|
async deleteRelationship(id) {
|
|
283
317
|
this.db.prepare('DELETE FROM relationships WHERE id = ?').run(id);
|
|
284
318
|
}
|
|
@@ -288,14 +322,14 @@ export class SQLiteStore {
|
|
|
288
322
|
const escaped = normalized.replace(/[%_\\]/g, '\\$&');
|
|
289
323
|
const pattern = escaped + '%';
|
|
290
324
|
return this.db.transaction(() => {
|
|
291
|
-
const relResult = this.db.prepare(`
|
|
292
|
-
DELETE FROM relationships
|
|
293
|
-
WHERE source_entity_id IN (SELECT id FROM entities WHERE source_file LIKE ? ESCAPE '\\')
|
|
294
|
-
OR target_entity_id IN (SELECT id FROM entities WHERE source_file LIKE ? ESCAPE '\\')
|
|
325
|
+
const relResult = this.db.prepare(`
|
|
326
|
+
DELETE FROM relationships
|
|
327
|
+
WHERE source_entity_id IN (SELECT id FROM entities WHERE source_file LIKE ? ESCAPE '\\')
|
|
328
|
+
OR target_entity_id IN (SELECT id FROM entities WHERE source_file LIKE ? ESCAPE '\\')
|
|
295
329
|
`).run(pattern, pattern);
|
|
296
|
-
this.db.prepare(`
|
|
297
|
-
DELETE FROM entities_fts
|
|
298
|
-
WHERE rowid IN (SELECT rowid FROM entities WHERE source_file LIKE ? ESCAPE '\\' AND deleted_at IS NULL)
|
|
330
|
+
this.db.prepare(`
|
|
331
|
+
DELETE FROM entities_fts
|
|
332
|
+
WHERE rowid IN (SELECT rowid FROM entities WHERE source_file LIKE ? ESCAPE '\\' AND deleted_at IS NULL)
|
|
299
333
|
`).run(pattern);
|
|
300
334
|
const entityResult = this.db.prepare("DELETE FROM entities WHERE source_file LIKE ? ESCAPE '\\'").run(pattern);
|
|
301
335
|
const fileResult = this.db.prepare("DELETE FROM files WHERE path LIKE ? ESCAPE '\\'").run(pattern);
|
|
@@ -317,14 +351,14 @@ export class SQLiteStore {
|
|
|
317
351
|
}
|
|
318
352
|
pruneSoftDeleted() {
|
|
319
353
|
return this.db.transaction(() => {
|
|
320
|
-
const relResult = this.db.prepare(`
|
|
321
|
-
DELETE FROM relationships
|
|
322
|
-
WHERE source_entity_id IN (SELECT id FROM entities WHERE deleted_at IS NOT NULL)
|
|
323
|
-
OR target_entity_id IN (SELECT id FROM entities WHERE deleted_at IS NOT NULL)
|
|
354
|
+
const relResult = this.db.prepare(`
|
|
355
|
+
DELETE FROM relationships
|
|
356
|
+
WHERE source_entity_id IN (SELECT id FROM entities WHERE deleted_at IS NOT NULL)
|
|
357
|
+
OR target_entity_id IN (SELECT id FROM entities WHERE deleted_at IS NOT NULL)
|
|
324
358
|
`).run();
|
|
325
|
-
this.db.prepare(`
|
|
326
|
-
DELETE FROM entities_fts
|
|
327
|
-
WHERE rowid IN (SELECT rowid FROM entities WHERE deleted_at IS NOT NULL)
|
|
359
|
+
this.db.prepare(`
|
|
360
|
+
DELETE FROM entities_fts
|
|
361
|
+
WHERE rowid IN (SELECT rowid FROM entities WHERE deleted_at IS NOT NULL)
|
|
328
362
|
`).run();
|
|
329
363
|
const entityResult = this.db.prepare('DELETE FROM entities WHERE deleted_at IS NOT NULL').run();
|
|
330
364
|
return {
|
|
@@ -337,22 +371,22 @@ export class SQLiteStore {
|
|
|
337
371
|
async upsertFile(file) {
|
|
338
372
|
const existing = this.db.prepare('SELECT * FROM files WHERE path = ?').get(file.path);
|
|
339
373
|
if (existing) {
|
|
340
|
-
this.db.prepare(`
|
|
341
|
-
UPDATE files SET
|
|
342
|
-
relative_path = ?, project_id = ?, content_hash = ?,
|
|
343
|
-
file_type = ?, size_bytes = ?, last_modified = ?,
|
|
344
|
-
last_ingested_at = ?, entity_ids = ?, status = ?, parse_error = ?
|
|
345
|
-
WHERE path = ?
|
|
374
|
+
this.db.prepare(`
|
|
375
|
+
UPDATE files SET
|
|
376
|
+
relative_path = ?, project_id = ?, content_hash = ?,
|
|
377
|
+
file_type = ?, size_bytes = ?, last_modified = ?,
|
|
378
|
+
last_ingested_at = ?, entity_ids = ?, status = ?, parse_error = ?
|
|
379
|
+
WHERE path = ?
|
|
346
380
|
`).run(file.relativePath, file.projectId, file.contentHash, file.fileType, file.sizeBytes, file.lastModified, file.lastIngestedAt ?? null, JSON.stringify(file.entityIds), file.status, file.parseError ?? null, file.path);
|
|
347
381
|
return { ...file, id: existing.id };
|
|
348
382
|
}
|
|
349
383
|
const id = randomUUID();
|
|
350
|
-
this.db.prepare(`
|
|
351
|
-
INSERT INTO files (
|
|
352
|
-
id, path, relative_path, project_id, content_hash,
|
|
353
|
-
file_type, size_bytes, last_modified, last_ingested_at,
|
|
354
|
-
entity_ids, status, parse_error
|
|
355
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
384
|
+
this.db.prepare(`
|
|
385
|
+
INSERT INTO files (
|
|
386
|
+
id, path, relative_path, project_id, content_hash,
|
|
387
|
+
file_type, size_bytes, last_modified, last_ingested_at,
|
|
388
|
+
entity_ids, status, parse_error
|
|
389
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
356
390
|
`).run(id, file.path, file.relativePath, file.projectId, file.contentHash, file.fileType, file.sizeBytes, file.lastModified, file.lastIngestedAt ?? null, JSON.stringify(file.entityIds), file.status, file.parseError ?? null);
|
|
357
391
|
return { ...file, id };
|
|
358
392
|
}
|
|
@@ -368,11 +402,11 @@ export class SQLiteStore {
|
|
|
368
402
|
async createProject(project) {
|
|
369
403
|
const id = randomUUID();
|
|
370
404
|
const ts = now();
|
|
371
|
-
this.db.prepare(`
|
|
372
|
-
INSERT INTO projects (
|
|
373
|
-
id, name, root_path, privacy_level,
|
|
374
|
-
file_count, entity_count, last_ingested_at, created_at
|
|
375
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
405
|
+
this.db.prepare(`
|
|
406
|
+
INSERT INTO projects (
|
|
407
|
+
id, name, root_path, privacy_level,
|
|
408
|
+
file_count, entity_count, last_ingested_at, created_at
|
|
409
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
376
410
|
`).run(id, project.name, project.rootPath, project.privacyLevel, project.fileCount, project.entityCount, project.lastIngestedAt ?? null, ts);
|
|
377
411
|
return { ...project, id, createdAt: ts };
|
|
378
412
|
}
|
|
@@ -387,11 +421,11 @@ export class SQLiteStore {
|
|
|
387
421
|
// --- Contradictions ---
|
|
388
422
|
async createContradiction(contradiction) {
|
|
389
423
|
const id = randomUUID();
|
|
390
|
-
this.db.prepare(`
|
|
391
|
-
INSERT INTO contradictions (
|
|
392
|
-
id, entity_id_a, entity_id_b, description, severity,
|
|
393
|
-
suggested_resolution, status, resolved_action, resolved_at, detected_at
|
|
394
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
424
|
+
this.db.prepare(`
|
|
425
|
+
INSERT INTO contradictions (
|
|
426
|
+
id, entity_id_a, entity_id_b, description, severity,
|
|
427
|
+
suggested_resolution, status, resolved_action, resolved_at, detected_at
|
|
428
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
395
429
|
`).run(id, contradiction.entityIds[0], contradiction.entityIds[1], contradiction.description, contradiction.severity, contradiction.suggestedResolution ?? null, contradiction.status, contradiction.resolvedAction ?? null, contradiction.resolvedAt ?? null, contradiction.detectedAt);
|
|
396
430
|
return { ...contradiction, id };
|
|
397
431
|
}
|
|
@@ -416,8 +450,8 @@ export class SQLiteStore {
|
|
|
416
450
|
return rows.map(rowToContradiction);
|
|
417
451
|
}
|
|
418
452
|
async updateContradiction(id, update) {
|
|
419
|
-
this.db.prepare(`
|
|
420
|
-
UPDATE contradictions SET status = ?, resolved_action = ?, resolved_at = ? WHERE id = ?
|
|
453
|
+
this.db.prepare(`
|
|
454
|
+
UPDATE contradictions SET status = ?, resolved_action = ?, resolved_at = ? WHERE id = ?
|
|
421
455
|
`).run(update.status, update.resolvedAction ?? null, update.resolvedAt ?? null, id);
|
|
422
456
|
}
|
|
423
457
|
// --- Search ---
|
|
@@ -426,12 +460,12 @@ export class SQLiteStore {
|
|
|
426
460
|
const sanitized = text.replace(/[^a-zA-Z0-9\s]/g, ' ').trim();
|
|
427
461
|
if (!sanitized)
|
|
428
462
|
return [];
|
|
429
|
-
const rows = this.db.prepare(`
|
|
430
|
-
SELECT e.* FROM entities e
|
|
431
|
-
JOIN entities_fts fts ON fts.rowid = e.rowid
|
|
432
|
-
WHERE fts.entities_fts MATCH ? AND e.deleted_at IS NULL
|
|
433
|
-
ORDER BY rank
|
|
434
|
-
LIMIT ?
|
|
463
|
+
const rows = this.db.prepare(`
|
|
464
|
+
SELECT e.* FROM entities e
|
|
465
|
+
JOIN entities_fts fts ON fts.rowid = e.rowid
|
|
466
|
+
WHERE fts.entities_fts MATCH ? AND e.deleted_at IS NULL
|
|
467
|
+
ORDER BY rank
|
|
468
|
+
LIMIT ?
|
|
435
469
|
`).all(sanitized, limit);
|
|
436
470
|
return rows.map(rowToEntity);
|
|
437
471
|
}
|
|
@@ -476,13 +510,13 @@ export class SQLiteStore {
|
|
|
476
510
|
}
|
|
477
511
|
}
|
|
478
512
|
// Failed files with error messages
|
|
479
|
-
const failedFiles = this.db.prepare(`SELECT path, relative_path, parse_error FROM files
|
|
480
|
-
WHERE status = 'failed' AND parse_error IS NOT NULL
|
|
513
|
+
const failedFiles = this.db.prepare(`SELECT path, relative_path, parse_error FROM files
|
|
514
|
+
WHERE status = 'failed' AND parse_error IS NOT NULL
|
|
481
515
|
ORDER BY path LIMIT 50`).all()
|
|
482
516
|
.map((r) => ({ path: r.path, relativePath: r.relative_path, parseError: r.parse_error }));
|
|
483
517
|
// Entity breakdown by type (active only)
|
|
484
|
-
const entityRows = this.db.prepare(`SELECT type, COUNT(*) as count, AVG(confidence) as avg_confidence
|
|
485
|
-
FROM entities WHERE deleted_at IS NULL AND status = 'active'
|
|
518
|
+
const entityRows = this.db.prepare(`SELECT type, COUNT(*) as count, AVG(confidence) as avg_confidence
|
|
519
|
+
FROM entities WHERE deleted_at IS NULL AND status = 'active'
|
|
486
520
|
GROUP BY type ORDER BY count DESC`).all();
|
|
487
521
|
const entityBreakdown = entityRows.map((r) => ({
|
|
488
522
|
type: r.type, count: r.count, avgConfidence: r.avg_confidence,
|
|
@@ -513,14 +547,14 @@ export class SQLiteStore {
|
|
|
513
547
|
contradictions.lowSeverity += r.count;
|
|
514
548
|
}
|
|
515
549
|
// Top active contradictions (high first, then medium, limit 10)
|
|
516
|
-
const topContrRows = this.db.prepare(`SELECT c.id, c.severity, c.description, ea.name as entity_a, eb.name as entity_b
|
|
517
|
-
FROM contradictions c
|
|
518
|
-
LEFT JOIN entities ea ON c.entity_id_a = ea.id
|
|
519
|
-
LEFT JOIN entities eb ON c.entity_id_b = eb.id
|
|
520
|
-
WHERE c.status = 'active'
|
|
521
|
-
ORDER BY CASE c.severity
|
|
522
|
-
WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3
|
|
523
|
-
END, c.detected_at DESC
|
|
550
|
+
const topContrRows = this.db.prepare(`SELECT c.id, c.severity, c.description, ea.name as entity_a, eb.name as entity_b
|
|
551
|
+
FROM contradictions c
|
|
552
|
+
LEFT JOIN entities ea ON c.entity_id_a = ea.id
|
|
553
|
+
LEFT JOIN entities eb ON c.entity_id_b = eb.id
|
|
554
|
+
WHERE c.status = 'active'
|
|
555
|
+
ORDER BY CASE c.severity
|
|
556
|
+
WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3
|
|
557
|
+
END, c.detected_at DESC
|
|
524
558
|
LIMIT 10`).all();
|
|
525
559
|
const topContradictions = topContrRows.map((r) => ({
|
|
526
560
|
id: r.id.slice(0, 8),
|
|
@@ -530,9 +564,9 @@ export class SQLiteStore {
|
|
|
530
564
|
entityB: r.entity_b ?? 'unknown',
|
|
531
565
|
}));
|
|
532
566
|
// Token estimate from entity extracted_by JSON
|
|
533
|
-
const tokenRow = this.db.prepare(`SELECT
|
|
534
|
-
SUM(CAST(JSON_EXTRACT(extracted_by, '$.tokensUsed.input') AS INTEGER)) as total_input,
|
|
535
|
-
SUM(CAST(JSON_EXTRACT(extracted_by, '$.tokensUsed.output') AS INTEGER)) as total_output
|
|
567
|
+
const tokenRow = this.db.prepare(`SELECT
|
|
568
|
+
SUM(CAST(JSON_EXTRACT(extracted_by, '$.tokensUsed.input') AS INTEGER)) as total_input,
|
|
569
|
+
SUM(CAST(JSON_EXTRACT(extracted_by, '$.tokensUsed.output') AS INTEGER)) as total_output
|
|
536
570
|
FROM entities WHERE deleted_at IS NULL`).get();
|
|
537
571
|
return {
|
|
538
572
|
generatedAt: new Date().toISOString(),
|
|
@@ -562,18 +596,18 @@ export class SQLiteStore {
|
|
|
562
596
|
async integrityCheck() {
|
|
563
597
|
const details = [];
|
|
564
598
|
// Check for orphaned relationships
|
|
565
|
-
const orphanedRels = this.db.prepare(`
|
|
566
|
-
SELECT COUNT(*) as count FROM relationships r
|
|
567
|
-
WHERE NOT EXISTS (SELECT 1 FROM entities WHERE id = r.source_entity_id)
|
|
568
|
-
OR NOT EXISTS (SELECT 1 FROM entities WHERE id = r.target_entity_id)
|
|
599
|
+
const orphanedRels = this.db.prepare(`
|
|
600
|
+
SELECT COUNT(*) as count FROM relationships r
|
|
601
|
+
WHERE NOT EXISTS (SELECT 1 FROM entities WHERE id = r.source_entity_id)
|
|
602
|
+
OR NOT EXISTS (SELECT 1 FROM entities WHERE id = r.target_entity_id)
|
|
569
603
|
`).get().count;
|
|
570
604
|
if (orphanedRels > 0) {
|
|
571
605
|
details.push(`Found ${orphanedRels} orphaned relationships`);
|
|
572
606
|
}
|
|
573
607
|
// Check for files referencing missing projects
|
|
574
|
-
const missingProjects = this.db.prepare(`
|
|
575
|
-
SELECT COUNT(*) as count FROM files f
|
|
576
|
-
WHERE NOT EXISTS (SELECT 1 FROM projects WHERE id = f.project_id)
|
|
608
|
+
const missingProjects = this.db.prepare(`
|
|
609
|
+
SELECT COUNT(*) as count FROM files f
|
|
610
|
+
WHERE NOT EXISTS (SELECT 1 FROM projects WHERE id = f.project_id)
|
|
577
611
|
`).get().count;
|
|
578
612
|
if (missingProjects > 0) {
|
|
579
613
|
details.push(`Found ${missingProjects} files referencing missing projects`);
|
|
@@ -604,8 +638,8 @@ export class SQLiteStore {
|
|
|
604
638
|
params.push(limit);
|
|
605
639
|
const entityRows = this.db.prepare(entitySql).all(...params);
|
|
606
640
|
const entityIds = new Set(entityRows.map(e => e.id));
|
|
607
|
-
const relRows = this.db.prepare(`SELECT id, type, source_entity_id, target_entity_id, confidence
|
|
608
|
-
FROM relationships
|
|
641
|
+
const relRows = this.db.prepare(`SELECT id, type, source_entity_id, target_entity_id, confidence
|
|
642
|
+
FROM relationships
|
|
609
643
|
LIMIT ?`).all(limit * 2);
|
|
610
644
|
// Only include edges where both endpoints are in our node set
|
|
611
645
|
const edges = relRows
|