context-vault 2.8.0 → 2.8.4
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/README.md +39 -375
- package/bin/cli.js +26 -67
- package/node_modules/@context-vault/core/package.json +10 -3
- package/node_modules/@context-vault/core/src/capture/file-ops.js +20 -2
- package/node_modules/@context-vault/core/src/capture/import-pipeline.js +0 -34
- package/node_modules/@context-vault/core/src/capture/importers.js +64 -37
- package/node_modules/@context-vault/core/src/capture/ingest-url.js +80 -44
- package/node_modules/@context-vault/core/src/constants.js +8 -0
- package/node_modules/@context-vault/core/src/core/config.js +65 -29
- package/node_modules/@context-vault/core/src/core/files.js +8 -15
- package/node_modules/@context-vault/core/src/core/frontmatter.js +22 -10
- package/node_modules/@context-vault/core/src/core/status.js +32 -15
- package/node_modules/@context-vault/core/src/index/db.js +47 -34
- package/node_modules/@context-vault/core/src/index/embed.js +15 -5
- package/node_modules/@context-vault/core/src/index.js +39 -6
- package/node_modules/@context-vault/core/src/retrieve/index.js +40 -8
- package/node_modules/@context-vault/core/src/server/helpers.js +8 -6
- package/node_modules/@context-vault/core/src/server/tools/context-status.js +24 -10
- package/node_modules/@context-vault/core/src/server/tools/delete-context.js +8 -3
- package/node_modules/@context-vault/core/src/server/tools/get-context.js +117 -35
- package/node_modules/@context-vault/core/src/server/tools/ingest-url.js +5 -4
- package/node_modules/@context-vault/core/src/server/tools/list-context.js +59 -18
- package/node_modules/@context-vault/core/src/server/tools/save-context.js +10 -10
- package/node_modules/@context-vault/core/src/server/tools.js +24 -28
- package/package.json +2 -2
- package/scripts/local-server.js +30 -30
- package/scripts/postinstall.js +25 -10
- package/scripts/prepack.js +18 -15
- package/src/server/index.js +78 -29
- package/app-dist/assets/index-DjXoWapE.css +0 -1
- package/app-dist/assets/index-R4n9Qz4U.js +0 -380
- package/app-dist/index.html +0 -16
- package/node_modules/@context-vault/core/src/server/types.js +0 -78
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* db.js — Database schema, initialization, and prepared statements
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import { unlinkSync, copyFileSync, existsSync } from "node:fs";
|
|
6
2
|
|
|
7
|
-
// ─── Native Module Error ────────────────────────────────────────────────────
|
|
8
|
-
|
|
9
3
|
export class NativeModuleError extends Error {
|
|
10
4
|
constructor(originalError) {
|
|
11
5
|
const diagnostic = formatNativeModuleError(originalError);
|
|
@@ -18,7 +12,7 @@ export class NativeModuleError extends Error {
|
|
|
18
12
|
function formatNativeModuleError(err) {
|
|
19
13
|
const msg = err.message || "";
|
|
20
14
|
const versionMatch = msg.match(
|
|
21
|
-
/was compiled against a different Node\.js version using\s+NODE_MODULE_VERSION (\d+)\. This version of Node\.js requires\s+NODE_MODULE_VERSION (\d+)
|
|
15
|
+
/was compiled against a different Node\.js version using\s+NODE_MODULE_VERSION (\d+)\. This version of Node\.js requires\s+NODE_MODULE_VERSION (\d+)/,
|
|
22
16
|
);
|
|
23
17
|
|
|
24
18
|
const lines = [
|
|
@@ -44,13 +38,12 @@ function formatNativeModuleError(err) {
|
|
|
44
38
|
return lines.join("\n");
|
|
45
39
|
}
|
|
46
40
|
|
|
47
|
-
// ─── Lazy Native Module Loading ─────────────────────────────────────────────
|
|
48
|
-
|
|
49
41
|
let _Database = null;
|
|
50
42
|
let _sqliteVec = null;
|
|
51
43
|
|
|
52
44
|
async function loadNativeModules() {
|
|
53
|
-
if (_Database && _sqliteVec)
|
|
45
|
+
if (_Database && _sqliteVec)
|
|
46
|
+
return { Database: _Database, sqliteVec: _sqliteVec };
|
|
54
47
|
|
|
55
48
|
try {
|
|
56
49
|
const dbMod = await import("better-sqlite3");
|
|
@@ -69,8 +62,6 @@ async function loadNativeModules() {
|
|
|
69
62
|
return { Database: _Database, sqliteVec: _sqliteVec };
|
|
70
63
|
}
|
|
71
64
|
|
|
72
|
-
// ─── Schema DDL (v7 — teams) ────────────────────────────────────────────────
|
|
73
|
-
|
|
74
65
|
export const SCHEMA_DDL = `
|
|
75
66
|
CREATE TABLE IF NOT EXISTS vault (
|
|
76
67
|
id TEXT PRIMARY KEY,
|
|
@@ -126,8 +117,6 @@ export const SCHEMA_DDL = `
|
|
|
126
117
|
CREATE VIRTUAL TABLE IF NOT EXISTS vault_vec USING vec0(embedding float[384]);
|
|
127
118
|
`;
|
|
128
119
|
|
|
129
|
-
// ─── Database Init ───────────────────────────────────────────────────────────
|
|
130
|
-
|
|
131
120
|
export async function initDatabase(dbPath) {
|
|
132
121
|
const { Database, sqliteVec } = await loadNativeModules();
|
|
133
122
|
|
|
@@ -148,7 +137,9 @@ export async function initDatabase(dbPath) {
|
|
|
148
137
|
|
|
149
138
|
// Enforce fresh-DB-only — old schemas get a full rebuild (with backup)
|
|
150
139
|
if (version > 0 && version < 5) {
|
|
151
|
-
console.error(
|
|
140
|
+
console.error(
|
|
141
|
+
`[context-vault] Schema v${version} is outdated. Rebuilding database...`,
|
|
142
|
+
);
|
|
152
143
|
|
|
153
144
|
// Backup old DB before destroying it
|
|
154
145
|
const backupPath = `${dbPath}.v${version}.backup`;
|
|
@@ -156,15 +147,23 @@ export async function initDatabase(dbPath) {
|
|
|
156
147
|
db.close();
|
|
157
148
|
if (existsSync(dbPath)) {
|
|
158
149
|
copyFileSync(dbPath, backupPath);
|
|
159
|
-
console.error(
|
|
150
|
+
console.error(
|
|
151
|
+
`[context-vault] Backed up old database to: ${backupPath}`,
|
|
152
|
+
);
|
|
160
153
|
}
|
|
161
154
|
} catch (backupErr) {
|
|
162
|
-
console.error(
|
|
155
|
+
console.error(
|
|
156
|
+
`[context-vault] Warning: could not backup old database: ${backupErr.message}`,
|
|
157
|
+
);
|
|
163
158
|
}
|
|
164
159
|
|
|
165
160
|
unlinkSync(dbPath);
|
|
166
|
-
try {
|
|
167
|
-
|
|
161
|
+
try {
|
|
162
|
+
unlinkSync(dbPath + "-wal");
|
|
163
|
+
} catch {}
|
|
164
|
+
try {
|
|
165
|
+
unlinkSync(dbPath + "-shm");
|
|
166
|
+
} catch {}
|
|
168
167
|
|
|
169
168
|
const freshDb = createDb(dbPath);
|
|
170
169
|
freshDb.exec(SCHEMA_DDL);
|
|
@@ -180,7 +179,9 @@ export async function initDatabase(dbPath) {
|
|
|
180
179
|
// Wrapped in transaction with duplicate-column guards for idempotent retry
|
|
181
180
|
const migrate = db.transaction(() => {
|
|
182
181
|
const addColumnSafe = (sql) => {
|
|
183
|
-
try {
|
|
182
|
+
try {
|
|
183
|
+
db.exec(sql);
|
|
184
|
+
} catch (e) {
|
|
184
185
|
if (!e.message.includes("duplicate column")) throw e;
|
|
185
186
|
}
|
|
186
187
|
};
|
|
@@ -193,14 +194,18 @@ export async function initDatabase(dbPath) {
|
|
|
193
194
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_vault_user ON vault(user_id)`);
|
|
194
195
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_vault_team ON vault(team_id)`);
|
|
195
196
|
db.exec(`DROP INDEX IF EXISTS idx_vault_identity`);
|
|
196
|
-
db.exec(
|
|
197
|
+
db.exec(
|
|
198
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_vault_identity ON vault(user_id, kind, identity_key) WHERE identity_key IS NOT NULL`,
|
|
199
|
+
);
|
|
197
200
|
db.pragma("user_version = 7");
|
|
198
201
|
});
|
|
199
202
|
migrate();
|
|
200
203
|
} else if (version === 6) {
|
|
201
204
|
// v6 -> v7 migration: add team_id column
|
|
202
205
|
const migrate = db.transaction(() => {
|
|
203
|
-
try {
|
|
206
|
+
try {
|
|
207
|
+
db.exec(`ALTER TABLE vault ADD COLUMN team_id TEXT`);
|
|
208
|
+
} catch (e) {
|
|
204
209
|
if (!e.message.includes("duplicate column")) throw e;
|
|
205
210
|
}
|
|
206
211
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_vault_team ON vault(team_id)`);
|
|
@@ -212,34 +217,42 @@ export async function initDatabase(dbPath) {
|
|
|
212
217
|
return db;
|
|
213
218
|
}
|
|
214
219
|
|
|
215
|
-
// ─── Prepared Statements Factory ─────────────────────────────────────────────
|
|
216
|
-
|
|
217
220
|
export function prepareStatements(db) {
|
|
218
221
|
try {
|
|
219
222
|
return {
|
|
220
|
-
insertEntry: db.prepare(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
insertEntry: db.prepare(
|
|
224
|
+
`INSERT INTO vault (id, user_id, kind, category, title, body, meta, tags, source, file_path, identity_key, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
225
|
+
),
|
|
226
|
+
insertEntryEncrypted: db.prepare(
|
|
227
|
+
`INSERT INTO vault (id, user_id, kind, category, title, body, meta, tags, source, file_path, identity_key, expires_at, created_at, body_encrypted, title_encrypted, meta_encrypted, iv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
228
|
+
),
|
|
229
|
+
updateEntry: db.prepare(
|
|
230
|
+
`UPDATE vault SET title = ?, body = ?, meta = ?, tags = ?, source = ?, category = ?, identity_key = ?, expires_at = ? WHERE file_path = ?`,
|
|
231
|
+
),
|
|
223
232
|
deleteEntry: db.prepare(`DELETE FROM vault WHERE id = ?`),
|
|
224
233
|
getRowid: db.prepare(`SELECT rowid FROM vault WHERE id = ?`),
|
|
225
234
|
getRowidByPath: db.prepare(`SELECT rowid FROM vault WHERE file_path = ?`),
|
|
226
235
|
getEntryById: db.prepare(`SELECT * FROM vault WHERE id = ?`),
|
|
227
|
-
getByIdentityKey: db.prepare(
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
getByIdentityKey: db.prepare(
|
|
237
|
+
`SELECT * FROM vault WHERE kind = ? AND identity_key = ? AND user_id IS ?`,
|
|
238
|
+
),
|
|
239
|
+
upsertByIdentityKey: db.prepare(
|
|
240
|
+
`UPDATE vault SET title = ?, body = ?, meta = ?, tags = ?, source = ?, category = ?, file_path = ?, expires_at = ? WHERE kind = ? AND identity_key = ? AND user_id IS ?`,
|
|
241
|
+
),
|
|
242
|
+
insertVecStmt: db.prepare(
|
|
243
|
+
`INSERT INTO vault_vec (rowid, embedding) VALUES (?, ?)`,
|
|
244
|
+
),
|
|
230
245
|
deleteVecStmt: db.prepare(`DELETE FROM vault_vec WHERE rowid = ?`),
|
|
231
246
|
};
|
|
232
247
|
} catch (e) {
|
|
233
248
|
throw new Error(
|
|
234
249
|
`Failed to prepare database statements. The database may be corrupted.\n` +
|
|
235
|
-
|
|
236
|
-
|
|
250
|
+
`Try deleting and rebuilding: rm "${db.name}" && context-vault reindex\n` +
|
|
251
|
+
`Original error: ${e.message}`,
|
|
237
252
|
);
|
|
238
253
|
}
|
|
239
254
|
}
|
|
240
255
|
|
|
241
|
-
// ─── Vector Helpers (parameterized rowid via cached statements) ──────────────
|
|
242
|
-
|
|
243
256
|
export function insertVec(stmts, rowid, embedding) {
|
|
244
257
|
// sqlite-vec requires BigInt for primary key — better-sqlite3 binds Number as REAL,
|
|
245
258
|
// but vec0 virtual tables only accept INTEGER rowids
|
|
@@ -30,14 +30,20 @@ async function ensurePipeline() {
|
|
|
30
30
|
mkdirSync(modelCacheDir, { recursive: true });
|
|
31
31
|
env.cacheDir = modelCacheDir;
|
|
32
32
|
|
|
33
|
-
console.error(
|
|
33
|
+
console.error(
|
|
34
|
+
"[context-vault] Loading embedding model (first run may download ~22MB)...",
|
|
35
|
+
);
|
|
34
36
|
extractor = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
|
|
35
37
|
embedAvailable = true;
|
|
36
38
|
return extractor;
|
|
37
39
|
} catch (e) {
|
|
38
40
|
embedAvailable = false;
|
|
39
|
-
console.error(
|
|
40
|
-
|
|
41
|
+
console.error(
|
|
42
|
+
`[context-vault] Failed to load embedding model: ${e.message}`,
|
|
43
|
+
);
|
|
44
|
+
console.error(
|
|
45
|
+
`[context-vault] Semantic search disabled. Full-text search still works.`,
|
|
46
|
+
);
|
|
41
47
|
return null;
|
|
42
48
|
}
|
|
43
49
|
}
|
|
@@ -74,9 +80,13 @@ export async function embedBatch(texts) {
|
|
|
74
80
|
}
|
|
75
81
|
const dim = result.data.length / texts.length;
|
|
76
82
|
if (!Number.isInteger(dim) || dim <= 0) {
|
|
77
|
-
throw new Error(
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Unexpected embedding dimension: ${result.data.length} / ${texts.length} = ${dim}`,
|
|
85
|
+
);
|
|
78
86
|
}
|
|
79
|
-
return texts.map(
|
|
87
|
+
return texts.map(
|
|
88
|
+
(_, i) => new Float32Array(result.data.buffer, i * dim * 4, dim),
|
|
89
|
+
);
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
/** Force re-initialization on next embed call. */
|
|
@@ -5,19 +5,47 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// Core utilities
|
|
8
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
categoryFor,
|
|
10
|
+
categoryDirFor,
|
|
11
|
+
CATEGORY_DIRS,
|
|
12
|
+
} from "./core/categories.js";
|
|
9
13
|
export { parseArgs, resolveConfig } from "./core/config.js";
|
|
10
|
-
export {
|
|
11
|
-
|
|
14
|
+
export {
|
|
15
|
+
ulid,
|
|
16
|
+
slugify,
|
|
17
|
+
kindToDir,
|
|
18
|
+
dirToKind,
|
|
19
|
+
normalizeKind,
|
|
20
|
+
kindToPath,
|
|
21
|
+
safeJoin,
|
|
22
|
+
walkDir,
|
|
23
|
+
} from "./core/files.js";
|
|
24
|
+
export {
|
|
25
|
+
formatFrontmatter,
|
|
26
|
+
parseFrontmatter,
|
|
27
|
+
extractCustomMeta,
|
|
28
|
+
parseEntryFromMarkdown,
|
|
29
|
+
} from "./core/frontmatter.js";
|
|
12
30
|
export { gatherVaultStatus } from "./core/status.js";
|
|
13
31
|
|
|
14
32
|
// Capture layer
|
|
15
|
-
export {
|
|
33
|
+
export {
|
|
34
|
+
writeEntry,
|
|
35
|
+
updateEntryFile,
|
|
36
|
+
captureAndIndex,
|
|
37
|
+
} from "./capture/index.js";
|
|
16
38
|
export { writeEntryFile } from "./capture/file-ops.js";
|
|
17
39
|
export { formatBody } from "./capture/formatters.js";
|
|
18
40
|
|
|
19
41
|
// Index layer
|
|
20
|
-
export {
|
|
42
|
+
export {
|
|
43
|
+
SCHEMA_DDL,
|
|
44
|
+
initDatabase,
|
|
45
|
+
prepareStatements,
|
|
46
|
+
insertVec,
|
|
47
|
+
deleteVec,
|
|
48
|
+
} from "./index/db.js";
|
|
21
49
|
export { embed, embedBatch, resetEmbedPipeline } from "./index/embed.js";
|
|
22
50
|
export { indexEntry, reindex } from "./index/index.js";
|
|
23
51
|
|
|
@@ -26,4 +54,9 @@ export { hybridSearch } from "./retrieve/index.js";
|
|
|
26
54
|
|
|
27
55
|
// Server tools & helpers
|
|
28
56
|
export { registerTools } from "./server/tools.js";
|
|
29
|
-
export {
|
|
57
|
+
export {
|
|
58
|
+
ok,
|
|
59
|
+
err,
|
|
60
|
+
ensureVaultExists,
|
|
61
|
+
ensureValidKind,
|
|
62
|
+
} from "./server/helpers.js";
|
|
@@ -38,7 +38,13 @@ export function recencyBoost(createdAt, category, decayDays = 30) {
|
|
|
38
38
|
* Build additional WHERE clauses for category/time filtering.
|
|
39
39
|
* Returns { clauses: string[], params: any[] }
|
|
40
40
|
*/
|
|
41
|
-
export function buildFilterClauses({
|
|
41
|
+
export function buildFilterClauses({
|
|
42
|
+
categoryFilter,
|
|
43
|
+
since,
|
|
44
|
+
until,
|
|
45
|
+
userIdFilter,
|
|
46
|
+
teamIdFilter,
|
|
47
|
+
}) {
|
|
42
48
|
const clauses = [];
|
|
43
49
|
const params = [];
|
|
44
50
|
if (userIdFilter !== undefined) {
|
|
@@ -76,10 +82,26 @@ export function buildFilterClauses({ categoryFilter, since, until, userIdFilter,
|
|
|
76
82
|
export async function hybridSearch(
|
|
77
83
|
ctx,
|
|
78
84
|
query,
|
|
79
|
-
{
|
|
85
|
+
{
|
|
86
|
+
kindFilter = null,
|
|
87
|
+
categoryFilter = null,
|
|
88
|
+
since = null,
|
|
89
|
+
until = null,
|
|
90
|
+
limit = 20,
|
|
91
|
+
offset = 0,
|
|
92
|
+
decayDays = 30,
|
|
93
|
+
userIdFilter,
|
|
94
|
+
teamIdFilter = null,
|
|
95
|
+
} = {},
|
|
80
96
|
) {
|
|
81
97
|
const results = new Map();
|
|
82
|
-
const extraFilters = buildFilterClauses({
|
|
98
|
+
const extraFilters = buildFilterClauses({
|
|
99
|
+
categoryFilter,
|
|
100
|
+
since,
|
|
101
|
+
until,
|
|
102
|
+
userIdFilter,
|
|
103
|
+
teamIdFilter,
|
|
104
|
+
});
|
|
83
105
|
|
|
84
106
|
// FTS5 search
|
|
85
107
|
const ftsQuery = buildFtsQuery(query);
|
|
@@ -126,10 +148,16 @@ export async function hybridSearch(
|
|
|
126
148
|
if (queryVec) {
|
|
127
149
|
// Increase limits in hosted mode to compensate for post-filtering
|
|
128
150
|
const hasPostFilter = userIdFilter !== undefined || teamIdFilter;
|
|
129
|
-
const vecLimit = hasPostFilter
|
|
151
|
+
const vecLimit = hasPostFilter
|
|
152
|
+
? kindFilter
|
|
153
|
+
? 60
|
|
154
|
+
: 30
|
|
155
|
+
: kindFilter
|
|
156
|
+
? 30
|
|
157
|
+
: 15;
|
|
130
158
|
const vecRows = ctx.db
|
|
131
159
|
.prepare(
|
|
132
|
-
`SELECT v.rowid, v.distance FROM vault_vec v WHERE embedding MATCH ? ORDER BY distance LIMIT ${vecLimit}
|
|
160
|
+
`SELECT v.rowid, v.distance FROM vault_vec v WHERE embedding MATCH ? ORDER BY distance LIMIT ${vecLimit}`,
|
|
133
161
|
)
|
|
134
162
|
.all(queryVec);
|
|
135
163
|
|
|
@@ -138,7 +166,9 @@ export async function hybridSearch(
|
|
|
138
166
|
const rowids = vecRows.map((vr) => vr.rowid);
|
|
139
167
|
const placeholders = rowids.map(() => "?").join(",");
|
|
140
168
|
const hydrated = ctx.db
|
|
141
|
-
.prepare(
|
|
169
|
+
.prepare(
|
|
170
|
+
`SELECT rowid, * FROM vault WHERE rowid IN (${placeholders})`,
|
|
171
|
+
)
|
|
142
172
|
.all(...rowids);
|
|
143
173
|
|
|
144
174
|
const byRowid = new Map();
|
|
@@ -147,13 +177,15 @@ export async function hybridSearch(
|
|
|
147
177
|
for (const vr of vecRows) {
|
|
148
178
|
const row = byRowid.get(vr.rowid);
|
|
149
179
|
if (!row) continue;
|
|
150
|
-
if (userIdFilter !== undefined && row.user_id !== userIdFilter)
|
|
180
|
+
if (userIdFilter !== undefined && row.user_id !== userIdFilter)
|
|
181
|
+
continue;
|
|
151
182
|
if (teamIdFilter && row.team_id !== teamIdFilter) continue;
|
|
152
183
|
if (kindFilter && row.kind !== kindFilter) continue;
|
|
153
184
|
if (categoryFilter && row.category !== categoryFilter) continue;
|
|
154
185
|
if (since && row.created_at < since) continue;
|
|
155
186
|
if (until && row.created_at > until) continue;
|
|
156
|
-
if (row.expires_at && new Date(row.expires_at) <= new Date())
|
|
187
|
+
if (row.expires_at && new Date(row.expires_at) <= new Date())
|
|
188
|
+
continue;
|
|
157
189
|
|
|
158
190
|
const { rowid: _rowid, ...cleanRow } = row;
|
|
159
191
|
// sqlite-vec returns L2 distance [0, 2] for normalized vectors.
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* helpers.js — Shared MCP response helpers and validation
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
// ─── MCP Response Helpers ────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
5
|
export function ok(text) {
|
|
8
6
|
return { content: [{ type: "text", text }] };
|
|
9
7
|
}
|
|
@@ -12,18 +10,22 @@ export function err(text, code = "UNKNOWN") {
|
|
|
12
10
|
return { content: [{ type: "text", text }], isError: true, code };
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
// ─── Validation Helpers ──────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
13
|
export function ensureVaultExists(config) {
|
|
18
14
|
if (!config.vaultDirExists) {
|
|
19
|
-
return err(
|
|
15
|
+
return err(
|
|
16
|
+
`Vault directory not found: ${config.vaultDir}. Run context_status for diagnostics.`,
|
|
17
|
+
"VAULT_NOT_FOUND",
|
|
18
|
+
);
|
|
20
19
|
}
|
|
21
20
|
return null;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
export function ensureValidKind(kind) {
|
|
25
24
|
if (!/^[a-z][a-z0-9_-]*$/.test(kind)) {
|
|
26
|
-
return err(
|
|
25
|
+
return err(
|
|
26
|
+
"Required: kind (lowercase alphanumeric, e.g. 'insight', 'reference')",
|
|
27
|
+
"INVALID_KIND",
|
|
28
|
+
);
|
|
27
29
|
}
|
|
28
30
|
return null;
|
|
29
31
|
}
|
|
@@ -18,7 +18,7 @@ export function handler(_args, ctx) {
|
|
|
18
18
|
|
|
19
19
|
const status = gatherVaultStatus(ctx, { userId });
|
|
20
20
|
|
|
21
|
-
const hasIssues = status.stalePaths ||
|
|
21
|
+
const hasIssues = status.stalePaths || status.embeddingStatus?.missing > 0;
|
|
22
22
|
const healthIcon = hasIssues ? "⚠" : "✓";
|
|
23
23
|
|
|
24
24
|
const lines = [
|
|
@@ -39,13 +39,17 @@ export function handler(_args, ctx) {
|
|
|
39
39
|
lines.push(`Embeddings: ${indexed}/${total} (${pct}%)`);
|
|
40
40
|
}
|
|
41
41
|
if (status.embedModelAvailable === false) {
|
|
42
|
-
lines.push(
|
|
42
|
+
lines.push(
|
|
43
|
+
`Embed model: unavailable (semantic search disabled, FTS still works)`,
|
|
44
|
+
);
|
|
43
45
|
} else if (status.embedModelAvailable === true) {
|
|
44
46
|
lines.push(`Embed model: loaded`);
|
|
45
47
|
}
|
|
46
48
|
lines.push(`Decay: ${config.eventDecayDays} days (event recency window)`);
|
|
47
49
|
if (status.expiredCount > 0) {
|
|
48
|
-
lines.push(
|
|
50
|
+
lines.push(
|
|
51
|
+
`Expired: ${status.expiredCount} entries (pruned on next reindex)`,
|
|
52
|
+
);
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
lines.push(``, `### Indexed`);
|
|
@@ -59,28 +63,38 @@ export function handler(_args, ctx) {
|
|
|
59
63
|
if (status.categoryCounts.length) {
|
|
60
64
|
lines.push(``);
|
|
61
65
|
lines.push(`### Categories`);
|
|
62
|
-
for (const { category, c } of status.categoryCounts)
|
|
66
|
+
for (const { category, c } of status.categoryCounts)
|
|
67
|
+
lines.push(`- ${category}: ${c}`);
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
if (status.subdirs.length) {
|
|
66
71
|
lines.push(``);
|
|
67
72
|
lines.push(`### Disk Directories`);
|
|
68
|
-
for (const { name, count } of status.subdirs)
|
|
73
|
+
for (const { name, count } of status.subdirs)
|
|
74
|
+
lines.push(`- ${name}/: ${count} files`);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
if (status.stalePaths) {
|
|
72
78
|
lines.push(``);
|
|
73
79
|
lines.push(`### ⚠ Stale Paths`);
|
|
74
|
-
lines.push(
|
|
80
|
+
lines.push(
|
|
81
|
+
`DB contains ${status.staleCount} paths not matching current vault dir.`,
|
|
82
|
+
);
|
|
75
83
|
lines.push(`Auto-reindex will fix this on next search or save.`);
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
// Suggested actions
|
|
79
87
|
const actions = [];
|
|
80
|
-
if (status.stalePaths)
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
88
|
+
if (status.stalePaths)
|
|
89
|
+
actions.push("- Run `context-vault reindex` to fix stale paths");
|
|
90
|
+
if (status.embeddingStatus?.missing > 0)
|
|
91
|
+
actions.push(
|
|
92
|
+
"- Run `context-vault reindex` to generate missing embeddings",
|
|
93
|
+
);
|
|
94
|
+
if (!config.vaultDirExists)
|
|
95
|
+
actions.push("- Run `context-vault setup` to create the vault directory");
|
|
96
|
+
if (status.kindCounts.length === 0 && config.vaultDirExists)
|
|
97
|
+
actions.push("- Use `save_context` to add your first entry");
|
|
84
98
|
|
|
85
99
|
if (actions.length) {
|
|
86
100
|
lines.push("", "### Suggested Actions", ...actions);
|
|
@@ -19,7 +19,8 @@ export const inputSchema = {
|
|
|
19
19
|
export async function handler({ id }, ctx, { ensureIndexed }) {
|
|
20
20
|
const userId = ctx.userId !== undefined ? ctx.userId : undefined;
|
|
21
21
|
|
|
22
|
-
if (!id?.trim())
|
|
22
|
+
if (!id?.trim())
|
|
23
|
+
return err("Required: id (non-empty string)", "INVALID_INPUT");
|
|
23
24
|
await ensureIndexed();
|
|
24
25
|
|
|
25
26
|
const entry = ctx.stmts.getEntryById.get(id);
|
|
@@ -32,13 +33,17 @@ export async function handler({ id }, ctx, { ensureIndexed }) {
|
|
|
32
33
|
|
|
33
34
|
// Delete file from disk first (source of truth)
|
|
34
35
|
if (entry.file_path) {
|
|
35
|
-
try {
|
|
36
|
+
try {
|
|
37
|
+
unlinkSync(entry.file_path);
|
|
38
|
+
} catch {}
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
// Delete vector embedding
|
|
39
42
|
const rowidResult = ctx.stmts.getRowid.get(id);
|
|
40
43
|
if (rowidResult?.rowid) {
|
|
41
|
-
try {
|
|
44
|
+
try {
|
|
45
|
+
ctx.deleteVec(Number(rowidResult.rowid));
|
|
46
|
+
} catch {}
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
// Delete DB row (FTS trigger handles FTS cleanup)
|