@minhpnq1807/contextos 0.5.1 → 0.5.2

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.2
4
+
5
+ - Recovers automatically from malformed `embeddings.db` files by moving the corrupt cache aside and recreating it.
6
+ - Writes the sql.js embedding cache with atomic temp-file rename to avoid partial DB files during concurrent hook/MCP writes.
7
+
3
8
  ## 0.5.1
4
9
 
5
10
  - Fixes `ctx sync --skills` first-run ordering by running `skillshare init` before `skillshare backup`, matching skillshare's config requirement.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhpnq1807/contextos",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Task-aware AGENTS.md context injection and compliance reporting for Codex, Claude Code, and Antigravity.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -158,21 +158,11 @@ async function getCachedEmbedding({ cache, embedder, text, sources }) {
158
158
  return embedding;
159
159
  }
160
160
 
161
- async function openEmbeddingCache(dataDir) {
161
+ export async function openEmbeddingCache(dataDir) {
162
162
  fs.mkdirSync(dataDir, { recursive: true });
163
163
  const cachePath = path.join(dataDir, "embeddings.db");
164
164
  const SQL = await getSql();
165
- const buffer = fs.existsSync(cachePath) ? fs.readFileSync(cachePath) : null;
166
- const db = buffer?.length ? new SQL.Database(buffer) : new SQL.Database();
167
-
168
- db.run(`
169
- CREATE TABLE IF NOT EXISTS embeddings (
170
- key TEXT PRIMARY KEY,
171
- model TEXT NOT NULL,
172
- vector TEXT NOT NULL,
173
- updated_at TEXT NOT NULL
174
- )
175
- `);
165
+ const db = initializeEmbeddingDatabase(SQL, cachePath);
176
166
 
177
167
  return {
178
168
  path: cachePath,
@@ -191,15 +181,79 @@ async function openEmbeddingCache(dataDir) {
191
181
  "INSERT OR REPLACE INTO embeddings (key, model, vector, updated_at) VALUES (?, ?, ?, ?)",
192
182
  [key, DEFAULT_MODEL, JSON.stringify(vector), new Date().toISOString()]
193
183
  );
194
- fs.writeFileSync(cachePath, Buffer.from(db.export()));
184
+ writeDatabaseAtomically(cachePath, db);
195
185
  },
196
186
  close() {
197
- fs.writeFileSync(cachePath, Buffer.from(db.export()));
187
+ writeDatabaseAtomically(cachePath, db);
198
188
  db.close();
199
189
  }
200
190
  };
201
191
  }
202
192
 
193
+ function initializeEmbeddingDatabase(SQL, cachePath) {
194
+ let db = openSqlDatabase(SQL, cachePath);
195
+ try {
196
+ ensureEmbeddingSchema(db);
197
+ return db;
198
+ } catch (error) {
199
+ try {
200
+ db.close();
201
+ } catch {
202
+ // Ignore close failures while recovering a corrupt cache.
203
+ }
204
+ quarantineMalformedCache(cachePath, error);
205
+ db = new SQL.Database();
206
+ ensureEmbeddingSchema(db);
207
+ writeDatabaseAtomically(cachePath, db);
208
+ return db;
209
+ }
210
+ }
211
+
212
+ function ensureEmbeddingSchema(db) {
213
+ db.run(`
214
+ CREATE TABLE IF NOT EXISTS embeddings (
215
+ key TEXT PRIMARY KEY,
216
+ model TEXT NOT NULL,
217
+ vector TEXT NOT NULL,
218
+ updated_at TEXT NOT NULL
219
+ )
220
+ `);
221
+ }
222
+
223
+ function openSqlDatabase(SQL, cachePath) {
224
+ if (!fs.existsSync(cachePath)) return new SQL.Database();
225
+ const buffer = fs.readFileSync(cachePath);
226
+ if (!buffer.length) return new SQL.Database();
227
+ try {
228
+ return new SQL.Database(buffer);
229
+ } catch (error) {
230
+ quarantineMalformedCache(cachePath, error);
231
+ return new SQL.Database();
232
+ }
233
+ }
234
+
235
+ function quarantineMalformedCache(cachePath, error) {
236
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
237
+ const corruptPath = `${cachePath}.corrupt-${stamp}-${process.pid}`;
238
+ try {
239
+ fs.renameSync(cachePath, corruptPath);
240
+ console.warn(`[ctx] Embedding cache was malformed and has been moved to ${corruptPath}: ${error?.message || error}`);
241
+ } catch {
242
+ try {
243
+ fs.rmSync(cachePath, { force: true });
244
+ console.warn(`[ctx] Embedding cache was malformed and has been reset: ${error?.message || error}`);
245
+ } catch {
246
+ // The caller will recreate the cache if the old file could not be moved.
247
+ }
248
+ }
249
+ }
250
+
251
+ function writeDatabaseAtomically(cachePath, db) {
252
+ const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
253
+ fs.writeFileSync(tmpPath, Buffer.from(db.export()));
254
+ fs.renameSync(tmpPath, cachePath);
255
+ }
256
+
203
257
  async function getSql() {
204
258
  if (!sqlPromise) {
205
259
  sqlPromise = (async () => {