recallx 1.0.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.
Files changed (37) hide show
  1. package/README.md +205 -0
  2. package/app/cli/bin/recallx-mcp.js +2 -0
  3. package/app/cli/bin/recallx.js +8 -0
  4. package/app/cli/src/cli.js +808 -0
  5. package/app/cli/src/format.js +242 -0
  6. package/app/cli/src/http.js +35 -0
  7. package/app/mcp/api-client.js +101 -0
  8. package/app/mcp/index.js +128 -0
  9. package/app/mcp/server.js +786 -0
  10. package/app/server/app.js +2263 -0
  11. package/app/server/config.js +27 -0
  12. package/app/server/db.js +399 -0
  13. package/app/server/errors.js +17 -0
  14. package/app/server/governance.js +466 -0
  15. package/app/server/index.js +26 -0
  16. package/app/server/inferred-relations.js +247 -0
  17. package/app/server/observability.js +495 -0
  18. package/app/server/project-graph.js +199 -0
  19. package/app/server/relation-scoring.js +59 -0
  20. package/app/server/repositories.js +2992 -0
  21. package/app/server/retrieval.js +486 -0
  22. package/app/server/semantic/chunker.js +85 -0
  23. package/app/server/semantic/provider.js +124 -0
  24. package/app/server/semantic/types.js +1 -0
  25. package/app/server/semantic/vector-store.js +169 -0
  26. package/app/server/utils.js +43 -0
  27. package/app/server/workspace-session.js +128 -0
  28. package/app/server/workspace.js +79 -0
  29. package/app/shared/contracts.js +268 -0
  30. package/app/shared/request-runtime.js +30 -0
  31. package/app/shared/types.js +1 -0
  32. package/app/shared/version.js +1 -0
  33. package/dist/renderer/assets/ProjectGraphCanvas-BMvz9DmE.js +312 -0
  34. package/dist/renderer/assets/index-C2-KXqBO.css +1 -0
  35. package/dist/renderer/assets/index-CrDu22h7.js +76 -0
  36. package/dist/renderer/index.html +13 -0
  37. package/package.json +49 -0
@@ -0,0 +1,27 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { getSchemaVersion } from "./db.js";
3
+ import { defaultWorkspaceName } from "./workspace.js";
4
+ export function createServerConfig(workspaceRoot) {
5
+ return {
6
+ port: Number(process.env.RECALLX_PORT ?? 8787),
7
+ bindAddress: process.env.RECALLX_BIND ?? "127.0.0.1",
8
+ apiToken: process.env.RECALLX_API_TOKEN ?? null,
9
+ workspaceName: process.env.RECALLX_WORKSPACE_NAME ?? defaultWorkspaceName(workspaceRoot)
10
+ };
11
+ }
12
+ export function ensureApiToken(config) {
13
+ if (config.apiToken) {
14
+ return config.apiToken;
15
+ }
16
+ return randomBytes(24).toString("hex");
17
+ }
18
+ export function workspaceInfo(rootPath, config, authMode) {
19
+ return {
20
+ rootPath,
21
+ workspaceName: config.workspaceName,
22
+ schemaVersion: getSchemaVersion(),
23
+ bindAddress: `${config.bindAddress}:${config.port}`,
24
+ enabledIntegrationModes: ["read-only", "append-only"],
25
+ authMode
26
+ };
27
+ }
@@ -0,0 +1,399 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { load as loadSqliteVec } from "sqlite-vec";
3
+ const schemaVersion = 7;
4
+ const sqliteVecStateByDb = new WeakMap();
5
+ function execMigration(db) {
6
+ db.exec(`
7
+ PRAGMA foreign_keys = ON;
8
+
9
+ CREATE TABLE IF NOT EXISTS settings (
10
+ key TEXT PRIMARY KEY,
11
+ value_json TEXT NOT NULL
12
+ );
13
+
14
+ CREATE TABLE IF NOT EXISTS nodes (
15
+ id TEXT PRIMARY KEY,
16
+ type TEXT NOT NULL,
17
+ status TEXT NOT NULL DEFAULT 'active',
18
+ canonicality TEXT NOT NULL DEFAULT 'canonical',
19
+ visibility TEXT NOT NULL DEFAULT 'normal',
20
+ title TEXT,
21
+ body TEXT,
22
+ summary TEXT,
23
+ created_by TEXT,
24
+ source_type TEXT,
25
+ source_label TEXT,
26
+ created_at TEXT NOT NULL,
27
+ updated_at TEXT NOT NULL,
28
+ tags_json TEXT,
29
+ metadata_json TEXT
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS node_tags (
33
+ node_id TEXT NOT NULL,
34
+ tag TEXT NOT NULL,
35
+ PRIMARY KEY (node_id, tag),
36
+ FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE
37
+ );
38
+
39
+ CREATE TABLE IF NOT EXISTS node_index_state (
40
+ node_id TEXT PRIMARY KEY,
41
+ content_hash TEXT,
42
+ embedding_status TEXT NOT NULL DEFAULT 'pending',
43
+ embedding_provider TEXT,
44
+ embedding_model TEXT,
45
+ embedding_version TEXT,
46
+ stale_reason TEXT,
47
+ updated_at TEXT NOT NULL,
48
+ FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE
49
+ );
50
+
51
+ CREATE TABLE IF NOT EXISTS node_chunks (
52
+ node_id TEXT NOT NULL,
53
+ ordinal INTEGER NOT NULL,
54
+ chunk_hash TEXT NOT NULL,
55
+ chunk_text TEXT NOT NULL,
56
+ token_count INTEGER,
57
+ start_offset INTEGER,
58
+ end_offset INTEGER,
59
+ updated_at TEXT NOT NULL,
60
+ PRIMARY KEY (node_id, ordinal),
61
+ FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE
62
+ );
63
+
64
+ CREATE TABLE IF NOT EXISTS node_embeddings (
65
+ owner_type TEXT NOT NULL,
66
+ owner_id TEXT NOT NULL,
67
+ chunk_ordinal INTEGER,
68
+ vector_ref TEXT,
69
+ vector_blob BLOB,
70
+ embedding_provider TEXT,
71
+ embedding_model TEXT,
72
+ embedding_version TEXT,
73
+ content_hash TEXT,
74
+ status TEXT NOT NULL DEFAULT 'pending',
75
+ created_at TEXT NOT NULL,
76
+ updated_at TEXT NOT NULL,
77
+ PRIMARY KEY (owner_type, owner_id, chunk_ordinal)
78
+ );
79
+
80
+ CREATE TABLE IF NOT EXISTS relations (
81
+ id TEXT PRIMARY KEY,
82
+ from_node_id TEXT NOT NULL,
83
+ to_node_id TEXT NOT NULL,
84
+ relation_type TEXT NOT NULL,
85
+ status TEXT NOT NULL DEFAULT 'active',
86
+ created_by TEXT,
87
+ source_type TEXT,
88
+ source_label TEXT,
89
+ created_at TEXT NOT NULL,
90
+ metadata_json TEXT,
91
+ FOREIGN KEY (from_node_id) REFERENCES nodes(id),
92
+ FOREIGN KEY (to_node_id) REFERENCES nodes(id)
93
+ );
94
+
95
+ CREATE TABLE IF NOT EXISTS activities (
96
+ id TEXT PRIMARY KEY,
97
+ target_node_id TEXT NOT NULL,
98
+ activity_type TEXT NOT NULL,
99
+ body TEXT,
100
+ created_by TEXT,
101
+ source_type TEXT,
102
+ source_label TEXT,
103
+ created_at TEXT NOT NULL,
104
+ metadata_json TEXT,
105
+ FOREIGN KEY (target_node_id) REFERENCES nodes(id)
106
+ );
107
+
108
+ CREATE TABLE IF NOT EXISTS inferred_relations (
109
+ id TEXT PRIMARY KEY,
110
+ from_node_id TEXT NOT NULL,
111
+ to_node_id TEXT NOT NULL,
112
+ relation_type TEXT NOT NULL,
113
+ base_score REAL NOT NULL,
114
+ usage_score REAL NOT NULL DEFAULT 0,
115
+ final_score REAL NOT NULL,
116
+ status TEXT NOT NULL DEFAULT 'active',
117
+ generator TEXT NOT NULL,
118
+ evidence_json TEXT NOT NULL,
119
+ last_computed_at TEXT NOT NULL,
120
+ expires_at TEXT,
121
+ metadata_json TEXT,
122
+ FOREIGN KEY (from_node_id) REFERENCES nodes(id),
123
+ FOREIGN KEY (to_node_id) REFERENCES nodes(id)
124
+ );
125
+
126
+ CREATE TABLE IF NOT EXISTS relation_usage_events (
127
+ id TEXT PRIMARY KEY,
128
+ relation_id TEXT NOT NULL,
129
+ relation_source TEXT NOT NULL,
130
+ event_type TEXT NOT NULL,
131
+ session_id TEXT,
132
+ run_id TEXT,
133
+ actor_type TEXT,
134
+ actor_label TEXT,
135
+ tool_name TEXT,
136
+ delta REAL NOT NULL,
137
+ created_at TEXT NOT NULL,
138
+ metadata_json TEXT
139
+ );
140
+
141
+ CREATE TABLE IF NOT EXISTS relation_usage_rollups (
142
+ relation_id TEXT PRIMARY KEY,
143
+ total_delta REAL NOT NULL DEFAULT 0,
144
+ event_count INTEGER NOT NULL DEFAULT 0,
145
+ last_event_at TEXT NOT NULL,
146
+ last_event_rowid INTEGER NOT NULL DEFAULT 0,
147
+ updated_at TEXT NOT NULL
148
+ );
149
+
150
+ CREATE TABLE IF NOT EXISTS relation_usage_rollup_state (
151
+ id TEXT PRIMARY KEY,
152
+ last_event_rowid INTEGER NOT NULL DEFAULT 0,
153
+ updated_at TEXT NOT NULL
154
+ );
155
+
156
+ CREATE TABLE IF NOT EXISTS search_feedback_events (
157
+ id TEXT PRIMARY KEY,
158
+ result_type TEXT NOT NULL,
159
+ result_id TEXT NOT NULL,
160
+ verdict TEXT NOT NULL,
161
+ query TEXT,
162
+ session_id TEXT,
163
+ run_id TEXT,
164
+ actor_type TEXT,
165
+ actor_label TEXT,
166
+ tool_name TEXT,
167
+ confidence REAL NOT NULL,
168
+ delta REAL NOT NULL,
169
+ created_at TEXT NOT NULL,
170
+ metadata_json TEXT
171
+ );
172
+
173
+ CREATE TABLE IF NOT EXISTS search_feedback_rollups (
174
+ result_type TEXT NOT NULL,
175
+ result_id TEXT NOT NULL,
176
+ total_delta REAL NOT NULL DEFAULT 0,
177
+ event_count INTEGER NOT NULL DEFAULT 0,
178
+ useful_count INTEGER NOT NULL DEFAULT 0,
179
+ not_useful_count INTEGER NOT NULL DEFAULT 0,
180
+ uncertain_count INTEGER NOT NULL DEFAULT 0,
181
+ last_event_at TEXT NOT NULL,
182
+ updated_at TEXT NOT NULL,
183
+ PRIMARY KEY (result_type, result_id)
184
+ );
185
+
186
+ CREATE TABLE IF NOT EXISTS governance_events (
187
+ id TEXT PRIMARY KEY,
188
+ entity_type TEXT NOT NULL,
189
+ entity_id TEXT NOT NULL,
190
+ event_type TEXT NOT NULL,
191
+ previous_state TEXT,
192
+ next_state TEXT NOT NULL,
193
+ confidence REAL NOT NULL,
194
+ reason TEXT NOT NULL,
195
+ created_at TEXT NOT NULL,
196
+ metadata_json TEXT
197
+ );
198
+
199
+ CREATE TABLE IF NOT EXISTS governance_state (
200
+ entity_type TEXT NOT NULL,
201
+ entity_id TEXT NOT NULL,
202
+ state TEXT NOT NULL,
203
+ confidence REAL NOT NULL,
204
+ reasons_json TEXT NOT NULL,
205
+ last_evaluated_at TEXT NOT NULL,
206
+ last_transition_at TEXT NOT NULL,
207
+ metadata_json TEXT NOT NULL,
208
+ PRIMARY KEY (entity_type, entity_id)
209
+ );
210
+
211
+ CREATE TABLE IF NOT EXISTS artifacts (
212
+ id TEXT PRIMARY KEY,
213
+ node_id TEXT NOT NULL,
214
+ path TEXT NOT NULL,
215
+ mime_type TEXT,
216
+ size_bytes INTEGER,
217
+ checksum TEXT,
218
+ created_by TEXT,
219
+ source_label TEXT,
220
+ created_at TEXT NOT NULL,
221
+ metadata_json TEXT,
222
+ FOREIGN KEY (node_id) REFERENCES nodes(id)
223
+ );
224
+
225
+ CREATE TABLE IF NOT EXISTS provenance_events (
226
+ id TEXT PRIMARY KEY,
227
+ entity_type TEXT NOT NULL,
228
+ entity_id TEXT NOT NULL,
229
+ operation_type TEXT NOT NULL,
230
+ actor_type TEXT NOT NULL,
231
+ actor_label TEXT,
232
+ tool_name TEXT,
233
+ tool_version TEXT,
234
+ timestamp TEXT NOT NULL,
235
+ input_ref TEXT,
236
+ metadata_json TEXT
237
+ );
238
+
239
+ CREATE TABLE IF NOT EXISTS integrations (
240
+ id TEXT PRIMARY KEY,
241
+ name TEXT NOT NULL,
242
+ kind TEXT NOT NULL,
243
+ status TEXT NOT NULL,
244
+ capabilities_json TEXT,
245
+ config_json TEXT,
246
+ created_at TEXT NOT NULL,
247
+ updated_at TEXT NOT NULL
248
+ );
249
+
250
+ CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
251
+ id UNINDEXED,
252
+ title,
253
+ body,
254
+ summary,
255
+ content=''
256
+ );
257
+
258
+ CREATE VIRTUAL TABLE IF NOT EXISTS activities_fts USING fts5(
259
+ id UNINDEXED,
260
+ body,
261
+ content=''
262
+ );
263
+
264
+ CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
265
+ INSERT INTO nodes_fts(rowid, id, title, body, summary)
266
+ VALUES (new.rowid, new.id, coalesce(new.title, ''), coalesce(new.body, ''), coalesce(new.summary, ''));
267
+ END;
268
+
269
+ CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
270
+ INSERT INTO nodes_fts(nodes_fts, rowid, id, title, body, summary)
271
+ VALUES ('delete', old.rowid, old.id, old.title, old.body, old.summary);
272
+ END;
273
+
274
+ CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
275
+ INSERT INTO nodes_fts(nodes_fts, rowid, id, title, body, summary)
276
+ VALUES ('delete', old.rowid, old.id, old.title, old.body, old.summary);
277
+ INSERT INTO nodes_fts(rowid, id, title, body, summary)
278
+ VALUES (new.rowid, new.id, coalesce(new.title, ''), coalesce(new.body, ''), coalesce(new.summary, ''));
279
+ END;
280
+
281
+ CREATE TRIGGER IF NOT EXISTS activities_ai AFTER INSERT ON activities BEGIN
282
+ INSERT INTO activities_fts(rowid, id, body)
283
+ VALUES (new.rowid, new.id, coalesce(new.body, ''));
284
+ END;
285
+
286
+ CREATE TRIGGER IF NOT EXISTS activities_ad AFTER DELETE ON activities BEGIN
287
+ INSERT INTO activities_fts(activities_fts, rowid, id, body)
288
+ VALUES ('delete', old.rowid, old.id, old.body);
289
+ END;
290
+
291
+ CREATE TRIGGER IF NOT EXISTS activities_au AFTER UPDATE ON activities BEGIN
292
+ INSERT INTO activities_fts(activities_fts, rowid, id, body)
293
+ VALUES ('delete', old.rowid, old.id, old.body);
294
+ INSERT INTO activities_fts(rowid, id, body)
295
+ VALUES (new.rowid, new.id, coalesce(new.body, ''));
296
+ END;
297
+
298
+ CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);
299
+ CREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status);
300
+ CREATE INDEX IF NOT EXISTS idx_nodes_updated_at ON nodes(updated_at);
301
+ CREATE INDEX IF NOT EXISTS idx_node_tags_tag_node
302
+ ON node_tags(tag, node_id);
303
+ CREATE INDEX IF NOT EXISTS idx_node_index_state_status
304
+ ON node_index_state(embedding_status, updated_at DESC);
305
+ CREATE INDEX IF NOT EXISTS idx_node_chunks_hash
306
+ ON node_chunks(chunk_hash);
307
+ CREATE INDEX IF NOT EXISTS idx_node_embeddings_status
308
+ ON node_embeddings(status, updated_at DESC);
309
+ CREATE INDEX IF NOT EXISTS idx_node_embeddings_owner
310
+ ON node_embeddings(owner_type, owner_id);
311
+ CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_node_id);
312
+ CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_node_id);
313
+ CREATE INDEX IF NOT EXISTS idx_relations_from_status_created_at
314
+ ON relations(from_node_id, status, created_at DESC);
315
+ CREATE INDEX IF NOT EXISTS idx_relations_to_status_created_at
316
+ ON relations(to_node_id, status, created_at DESC);
317
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_inferred_relations_identity
318
+ ON inferred_relations(from_node_id, to_node_id, relation_type, generator);
319
+ CREATE INDEX IF NOT EXISTS idx_inferred_relations_from_score
320
+ ON inferred_relations(from_node_id, final_score DESC);
321
+ CREATE INDEX IF NOT EXISTS idx_inferred_relations_to_score
322
+ ON inferred_relations(to_node_id, final_score DESC);
323
+ CREATE INDEX IF NOT EXISTS idx_inferred_relations_status
324
+ ON inferred_relations(status);
325
+ CREATE INDEX IF NOT EXISTS idx_relation_usage_relation
326
+ ON relation_usage_events(relation_id);
327
+ CREATE INDEX IF NOT EXISTS idx_relation_usage_created_at
328
+ ON relation_usage_events(created_at);
329
+ CREATE INDEX IF NOT EXISTS idx_relation_usage_rollups_last_event_at
330
+ ON relation_usage_rollups(last_event_at);
331
+ CREATE INDEX IF NOT EXISTS idx_search_feedback_result
332
+ ON search_feedback_events(result_type, result_id, created_at DESC);
333
+ CREATE INDEX IF NOT EXISTS idx_search_feedback_created_at
334
+ ON search_feedback_events(created_at DESC);
335
+ CREATE INDEX IF NOT EXISTS idx_search_feedback_rollups_result
336
+ ON search_feedback_rollups(result_type, total_delta DESC, last_event_at DESC);
337
+ CREATE INDEX IF NOT EXISTS idx_governance_events_entity
338
+ ON governance_events(entity_type, entity_id, created_at DESC);
339
+ CREATE INDEX IF NOT EXISTS idx_governance_state_state
340
+ ON governance_state(state, confidence ASC, last_transition_at DESC);
341
+ CREATE INDEX IF NOT EXISTS idx_activities_target ON activities(target_node_id);
342
+ CREATE INDEX IF NOT EXISTS idx_activities_target_created_at
343
+ ON activities(target_node_id, created_at DESC);
344
+ CREATE INDEX IF NOT EXISTS idx_artifacts_node ON artifacts(node_id);
345
+ CREATE INDEX IF NOT EXISTS idx_artifacts_node_created_at
346
+ ON artifacts(node_id, created_at DESC);
347
+ CREATE INDEX IF NOT EXISTS idx_artifacts_path_created_at
348
+ ON artifacts(path, created_at DESC, node_id);
349
+ CREATE INDEX IF NOT EXISTS idx_provenance_entity ON provenance_events(entity_type, entity_id);
350
+ CREATE INDEX IF NOT EXISTS idx_provenance_entity_timestamp
351
+ ON provenance_events(entity_type, entity_id, timestamp DESC);
352
+ `);
353
+ }
354
+ function detectSqliteVecVersion(db) {
355
+ try {
356
+ const row = db.prepare(`SELECT vec_version() AS version`).get();
357
+ return typeof row?.version === "string" ? row.version : null;
358
+ }
359
+ catch {
360
+ return null;
361
+ }
362
+ }
363
+ export function getSqliteVecExtensionRuntime(db) {
364
+ return sqliteVecStateByDb.get(db) ?? {
365
+ isLoaded: false,
366
+ version: null,
367
+ loadError: null
368
+ };
369
+ }
370
+ export function openDatabase(paths, options = {}) {
371
+ const db = new DatabaseSync(paths.dbPath, { allowExtension: true });
372
+ const runtime = {
373
+ isLoaded: false,
374
+ version: null,
375
+ loadError: null
376
+ };
377
+ try {
378
+ (options.sqliteVecLoader ?? loadSqliteVec)(db);
379
+ runtime.isLoaded = true;
380
+ runtime.version = detectSqliteVecVersion(db);
381
+ }
382
+ catch (error) {
383
+ runtime.loadError = error instanceof Error ? error.message : String(error);
384
+ }
385
+ finally {
386
+ try {
387
+ db.enableLoadExtension(false);
388
+ }
389
+ catch {
390
+ // Keep startup resilient even if the runtime does not expose toggling.
391
+ }
392
+ sqliteVecStateByDb.set(db, runtime);
393
+ }
394
+ execMigration(db);
395
+ return db;
396
+ }
397
+ export function getSchemaVersion() {
398
+ return schemaVersion;
399
+ }
@@ -0,0 +1,17 @@
1
+ export class AppError extends Error {
2
+ code;
3
+ statusCode;
4
+ details;
5
+ constructor(statusCode, code, message, details) {
6
+ super(message);
7
+ this.statusCode = statusCode;
8
+ this.code = code;
9
+ this.details = details;
10
+ }
11
+ }
12
+ export function assertPresent(value, message, details) {
13
+ if (value === null || value === undefined) {
14
+ throw new AppError(404, "NOT_FOUND", message, details);
15
+ }
16
+ return value;
17
+ }