@massu/core 1.16.2 → 1.16.3

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.
@@ -26,16 +26,6 @@ var __copyProps = (to, from, except, desc) => {
26
26
  };
27
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
28
 
29
- // src/lib/memory-path.ts
30
- function encodeMemoryDirName(projectRoot) {
31
- return projectRoot.replace(/\//g, "-");
32
- }
33
- var init_memory_path = __esm({
34
- "src/lib/memory-path.ts"() {
35
- "use strict";
36
- }
37
- });
38
-
39
29
  // src/credentials.ts
40
30
  import { homedir } from "os";
41
31
  import { resolve } from "path";
@@ -104,6 +94,345 @@ var init_credentials = __esm({
104
94
  }
105
95
  });
106
96
 
97
+ // src/lib/fileLock.ts
98
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
99
+ import { dirname } from "path";
100
+ import * as lockfile from "proper-lockfile";
101
+ function readLockHolderPid(lockPath) {
102
+ try {
103
+ const raw = readFileSync2(`${lockPath}.pid`, "utf-8").trim();
104
+ const pid = Number.parseInt(raw, 10);
105
+ if (!Number.isFinite(pid) || pid <= 0) return null;
106
+ return pid;
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+ function busyWaitSync(ms) {
112
+ if (typeof SharedArrayBuffer === "undefined" || typeof Atomics === "undefined") {
113
+ throw new Error(
114
+ `withFileLockSync requires SharedArrayBuffer + Atomics for its retry-loop wait. This Node runtime does not provide them \u2014 refusing to fall back to a CPU spinloop. If you hit this in a sandboxed serverless env, the fix is to perform the lock-protected operation in a host runtime that supports Atomics.`
115
+ );
116
+ }
117
+ const sab = new SharedArrayBuffer(4);
118
+ const view = new Int32Array(sab);
119
+ Atomics.wait(view, 0, 0, ms);
120
+ }
121
+ function withFileLockSync(lockPath, fn, opts = {}) {
122
+ mkdirSync2(dirname(lockPath), { recursive: true });
123
+ const staleMs = opts.staleMs ?? 3e4;
124
+ const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
125
+ const pollIntervalMs = opts.pollIntervalMs ?? 100;
126
+ const now = opts.now ?? Date.now;
127
+ const sleep = opts.sleep ?? busyWaitSync;
128
+ const makeBusyError = opts.errorFactory ?? ((path, pid, retrySeconds, code) => new FileLockBusyError(path, pid, retrySeconds, code));
129
+ let release = null;
130
+ const deadline = now() + blockMs;
131
+ for (; ; ) {
132
+ try {
133
+ release = lockfile.lockSync(lockPath, {
134
+ stale: staleMs,
135
+ retries: 0,
136
+ realpath: false
137
+ });
138
+ try {
139
+ writeFileSync2(`${lockPath}.pid`, String(process.pid), "utf-8");
140
+ } catch {
141
+ }
142
+ break;
143
+ } catch (err) {
144
+ const e = err;
145
+ const code = e.code;
146
+ if (code !== "ELOCKED" && code !== "EBUSY") {
147
+ throw err;
148
+ }
149
+ if (now() >= deadline) {
150
+ const holderPid = readLockHolderPid(lockPath);
151
+ const remainingMs = Math.max(0, deadline - now());
152
+ const retryAfterSeconds = blockMs === 0 ? Math.round(staleMs / 1e3) : Math.round(remainingMs / 1e3);
153
+ throw makeBusyError(lockPath, holderPid, retryAfterSeconds, code);
154
+ }
155
+ sleep(pollIntervalMs);
156
+ }
157
+ }
158
+ try {
159
+ return fn();
160
+ } finally {
161
+ try {
162
+ if (release) release();
163
+ } catch {
164
+ }
165
+ try {
166
+ rmSync2(`${lockPath}.pid`, { force: true });
167
+ } catch {
168
+ }
169
+ }
170
+ }
171
+ var FileLockBusyError;
172
+ var init_fileLock = __esm({
173
+ "src/lib/fileLock.ts"() {
174
+ "use strict";
175
+ FileLockBusyError = class extends Error {
176
+ constructor(lockPath, holderPid, retryAfterSeconds, causeCode) {
177
+ const pidPart = holderPid != null ? `(PID=${holderPid})` : "(PID=unknown)";
178
+ super(`File lock at ${lockPath} held by another process ${pidPart} \u2014 try again in ${retryAfterSeconds}s`);
179
+ this.lockPath = lockPath;
180
+ this.holderPid = holderPid;
181
+ this.retryAfterSeconds = retryAfterSeconds;
182
+ this.causeCode = causeCode;
183
+ this.name = "FileLockBusyError";
184
+ }
185
+ lockPath;
186
+ holderPid;
187
+ retryAfterSeconds;
188
+ causeCode;
189
+ };
190
+ }
191
+ });
192
+
193
+ // src/lib/sqlite-loader.ts
194
+ import { createRequire } from "module";
195
+ import { spawnSync } from "child_process";
196
+ import { accessSync, appendFileSync, chmodSync as chmodSync2, constants as fsConstants, existsSync as existsSync2, mkdirSync as mkdirSync3 } from "fs";
197
+ import { dirname as dirname2, join } from "path";
198
+ function isNativeAbiError(err) {
199
+ const code = err?.code;
200
+ if (code === "ERR_DLOPEN_FAILED") return true;
201
+ const msg = err instanceof Error ? err.message : String(err);
202
+ return /NODE_MODULE_VERSION/.test(msg) || /was compiled against a different Node\.js version/.test(msg) || /ERR_DLOPEN_FAILED/.test(msg) || /\.node\b/.test(msg) && /dlopen|invalid ELF|mach-o|image not found|no such file|not a valid Win32/i.test(msg);
203
+ }
204
+ function loadBetterSqlite3(opts = {}) {
205
+ if (_testCtor) return _testCtor;
206
+ if (opts.fresh) bustNativeCache();
207
+ return req("better-sqlite3");
208
+ }
209
+ function bustNativeCache() {
210
+ try {
211
+ const cache = req.cache;
212
+ if (!cache) return;
213
+ for (const key of Object.keys(cache)) {
214
+ if (/better[-_]sqlite3|better_sqlite3\.node|[\\/]bindings[\\/]/.test(key)) {
215
+ delete cache[key];
216
+ }
217
+ }
218
+ } catch {
219
+ }
220
+ }
221
+ function openDatabase(dbPath, opts = {}) {
222
+ const { selfHeal: selfHealOpt = true, ...ctorOpts } = opts;
223
+ const selfHeal = selfHealOpt && process.env.MASSU_HOOK_RUNTIME !== "1";
224
+ const Ctor = loadBetterSqlite3();
225
+ try {
226
+ return new Ctor(dbPath, ctorOpts);
227
+ } catch (err) {
228
+ if (!isNativeAbiError(err)) {
229
+ throw err;
230
+ }
231
+ if (!selfHeal) {
232
+ recordHealEvent({ phase: "skipped", reason: "self-heal-disabled", abiTo: process.versions.modules });
233
+ throw new MemoryEngineUnusableError("abi-mismatch", detailOf(err));
234
+ }
235
+ const result = (_testHeal ?? attemptNativeHeal)(err);
236
+ if (!result.healed && !result.contended) {
237
+ throw new MemoryEngineUnusableError(result.reason ?? "heal-failed", result.detail ?? detailOf(err));
238
+ }
239
+ try {
240
+ const FreshCtor = loadBetterSqlite3({ fresh: true });
241
+ return new FreshCtor(dbPath, ctorOpts);
242
+ } catch (retryErr) {
243
+ throw new MemoryEngineUnusableError("heal-failed", detailOf(retryErr));
244
+ }
245
+ }
246
+ }
247
+ function attemptNativeHeal(err) {
248
+ const start = Date.now();
249
+ const abiTo = process.versions.modules;
250
+ const abiFrom = parseAbiFrom(err);
251
+ let pkgDir;
252
+ try {
253
+ pkgDir = dirname2(req.resolve("better-sqlite3/package.json"));
254
+ } catch {
255
+ recordHealEvent({ phase: "skipped", reason: "not-resolvable", abiFrom, abiTo });
256
+ return { healed: false, reason: "missing", abiFrom, abiTo, detail: "better-sqlite3 not resolvable" };
257
+ }
258
+ if (!isWritable(pkgDir)) {
259
+ recordHealEvent({ phase: "skipped", reason: "dir-not-writable", abiFrom, abiTo });
260
+ return { healed: false, reason: "heal-failed", abiFrom, abiTo, detail: `install dir not writable: ${pkgDir}` };
261
+ }
262
+ recordHealEvent({ phase: "attempt", reason: "abi-mismatch", abiFrom, abiTo });
263
+ try {
264
+ return withFileLockSync(
265
+ join(credentialsDir(), "native-heal.lock"),
266
+ () => runRebuild(pkgDir, abiFrom, abiTo, start),
267
+ { blockMs: 6e4, staleMs: 3e5 }
268
+ );
269
+ } catch (lockErr) {
270
+ const detail = lockErr instanceof Error ? lockErr.message : String(lockErr);
271
+ recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, detail: `lock: ${detail}` });
272
+ return { healed: false, contended: true, reason: "heal-failed", abiFrom, abiTo, detail: `heal lock contended: ${detail}` };
273
+ }
274
+ }
275
+ function runRebuild(pkgDir, abiFrom, abiTo, start) {
276
+ try {
277
+ const FreshCtor = loadBetterSqlite3({ fresh: true });
278
+ new FreshCtor(":memory:").close();
279
+ const res2 = {
280
+ healed: true,
281
+ abiFrom,
282
+ abiTo,
283
+ durationMs: Date.now() - start,
284
+ detail: "already healed by a concurrent process"
285
+ };
286
+ recordHealEvent({ phase: "success", reason: "already-healed", abiFrom, abiTo, durationMs: res2.durationMs });
287
+ return res2;
288
+ } catch (probeErr) {
289
+ if (!isNativeAbiError(probeErr)) {
290
+ return { healed: false, reason: "heal-failed", abiFrom, abiTo, durationMs: Date.now() - start, detail: detailOf(probeErr) };
291
+ }
292
+ }
293
+ const prebuildBin = resolveLocalBin(pkgDir, "prebuild-install");
294
+ if (prebuildBin) {
295
+ const r = spawnSync(process.execPath, [prebuildBin], {
296
+ cwd: pkgDir,
297
+ encoding: "utf-8",
298
+ timeout: 12e4
299
+ });
300
+ if (r.status === 0) {
301
+ const res2 = { healed: true, method: "prebuild-install", abiFrom, abiTo, durationMs: Date.now() - start };
302
+ recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
303
+ return res2;
304
+ }
305
+ }
306
+ const gypBin = resolveNodeGypBin();
307
+ if (gypBin && hasCompiler()) {
308
+ const r = spawnSync(process.execPath, [gypBin, "rebuild", "--release"], {
309
+ cwd: pkgDir,
310
+ encoding: "utf-8",
311
+ timeout: 3e5
312
+ });
313
+ if (r.status === 0) {
314
+ const res2 = { healed: true, method: "node-gyp", abiFrom, abiTo, durationMs: Date.now() - start };
315
+ recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
316
+ return res2;
317
+ }
318
+ }
319
+ const res = {
320
+ healed: false,
321
+ reason: "heal-failed",
322
+ abiFrom,
323
+ abiTo,
324
+ durationMs: Date.now() - start,
325
+ detail: prebuildBin ? "prebuild-install failed; node-gyp fallback unavailable or failed" : "no prebuild-install bin resolvable"
326
+ };
327
+ recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, durationMs: res.durationMs });
328
+ return res;
329
+ }
330
+ function recordHealEvent(event) {
331
+ try {
332
+ const rec = {
333
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
334
+ node: process.versions.node,
335
+ platform: process.platform,
336
+ arch: process.arch,
337
+ ...event
338
+ };
339
+ const dir = credentialsDir();
340
+ mkdirSync3(dir, { recursive: true });
341
+ const file = join(dir, "native-heal-events.jsonl");
342
+ appendFileSync(file, JSON.stringify(rec) + "\n", "utf-8");
343
+ try {
344
+ chmodSync2(file, 384);
345
+ } catch {
346
+ }
347
+ } catch {
348
+ }
349
+ }
350
+ function parseAbiFrom(err) {
351
+ const msg = err instanceof Error ? err.message : String(err ?? "");
352
+ const m = /NODE_MODULE_VERSION (\d+)/.exec(msg);
353
+ if (m && /^\d+$/.test(m[1])) return m[1];
354
+ return void 0;
355
+ }
356
+ function detailOf(err) {
357
+ const msg = err instanceof Error ? err.message : String(err);
358
+ return msg.slice(0, 300);
359
+ }
360
+ function isWritable(dir) {
361
+ try {
362
+ accessSync(dir, fsConstants.W_OK);
363
+ return true;
364
+ } catch {
365
+ return false;
366
+ }
367
+ }
368
+ function resolveLocalBin(pkgDir, name) {
369
+ const candidates = [
370
+ join(pkgDir, "node_modules", ".bin", name),
371
+ // nested install
372
+ join(pkgDir, "..", ".bin", name),
373
+ // hoisted (node_modules/.bin)
374
+ join(pkgDir, "..", "..", "node_modules", ".bin", name)
375
+ // workspace hoist
376
+ ];
377
+ for (const c of candidates) {
378
+ if (existsSync2(c)) return c;
379
+ }
380
+ return void 0;
381
+ }
382
+ function resolveNodeGypBin() {
383
+ try {
384
+ return req.resolve("node-gyp/bin/node-gyp.js");
385
+ } catch {
386
+ return void 0;
387
+ }
388
+ }
389
+ function hasCompiler() {
390
+ for (const cc of ["cc", "clang", "gcc"]) {
391
+ try {
392
+ const r = spawnSync(cc, ["--version"], { encoding: "utf-8", timeout: 5e3 });
393
+ if (r.status === 0) return true;
394
+ } catch {
395
+ }
396
+ }
397
+ return false;
398
+ }
399
+ var req, NATIVE_DB_REMEDY, MemoryEngineUnusableError, _testCtor, _testHeal;
400
+ var init_sqlite_loader = __esm({
401
+ "src/lib/sqlite-loader.ts"() {
402
+ "use strict";
403
+ init_credentials();
404
+ init_fileLock();
405
+ req = createRequire(import.meta.url);
406
+ NATIVE_DB_REMEDY = "Run 'massu heal' to rebuild the database engine for your Node version, then restart your MCP client / Claude Code.";
407
+ MemoryEngineUnusableError = class extends Error {
408
+ reason;
409
+ remedy;
410
+ detail;
411
+ constructor(reason, detail) {
412
+ super(
413
+ `Massu memory engine is unusable (${reason}). ${NATIVE_DB_REMEDY}` + (detail ? ` [${detail}]` : "")
414
+ );
415
+ this.name = "MemoryEngineUnusableError";
416
+ this.reason = reason;
417
+ this.remedy = NATIVE_DB_REMEDY;
418
+ this.detail = detail;
419
+ }
420
+ };
421
+ _testCtor = null;
422
+ _testHeal = null;
423
+ }
424
+ });
425
+
426
+ // src/lib/memory-path.ts
427
+ function encodeMemoryDirName(projectRoot) {
428
+ return projectRoot.replace(/\//g, "-");
429
+ }
430
+ var init_memory_path = __esm({
431
+ "src/lib/memory-path.ts"() {
432
+ "use strict";
433
+ }
434
+ });
435
+
107
436
  // src/config-memory-schema.ts
108
437
  import { z } from "zod";
109
438
  var MemoryConfigSchema;
@@ -243,8 +572,8 @@ var init_config_memory_schema = __esm({
243
572
  });
244
573
 
245
574
  // src/config.ts
246
- import { resolve as resolve2, dirname } from "path";
247
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
575
+ import { resolve as resolve2, dirname as dirname3 } from "path";
576
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
248
577
  import { homedir as homedir2 } from "os";
249
578
  import { parse as parseYaml } from "yaml";
250
579
  import { z as z2 } from "zod";
@@ -252,22 +581,22 @@ function findProjectRoot() {
252
581
  const cwd = process.cwd();
253
582
  let dir = cwd;
254
583
  while (true) {
255
- if (existsSync2(resolve2(dir, "massu.config.yaml"))) {
584
+ if (existsSync3(resolve2(dir, "massu.config.yaml"))) {
256
585
  return dir;
257
586
  }
258
- const parent = dirname(dir);
587
+ const parent = dirname3(dir);
259
588
  if (parent === dir) break;
260
589
  dir = parent;
261
590
  }
262
591
  dir = cwd;
263
592
  while (true) {
264
- if (existsSync2(resolve2(dir, "package.json"))) {
593
+ if (existsSync3(resolve2(dir, "package.json"))) {
265
594
  return dir;
266
595
  }
267
- if (existsSync2(resolve2(dir, ".git"))) {
596
+ if (existsSync3(resolve2(dir, ".git"))) {
268
597
  return dir;
269
598
  }
270
- const parent = dirname(dir);
599
+ const parent = dirname3(dir);
271
600
  if (parent === dir) break;
272
601
  dir = parent;
273
602
  }
@@ -284,8 +613,8 @@ function getConfig() {
284
613
  const root = getProjectRoot();
285
614
  const configPath = resolve2(root, "massu.config.yaml");
286
615
  let rawYaml = {};
287
- if (existsSync2(configPath)) {
288
- const content = readFileSync2(configPath, "utf-8");
616
+ if (existsSync3(configPath)) {
617
+ const content = readFileSync3(configPath, "utf-8");
289
618
  rawYaml = parseYaml(content) ?? {};
290
619
  }
291
620
  const result = RawConfigSchema.safeParse(rawYaml);
@@ -366,9 +695,13 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
366
695
  const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
367
696
  if (resolvedKey.apiKey) {
368
697
  _config.cloud = {
369
- enabled: true,
370
698
  sync: { memory: true, analytics: true, audit: true },
371
699
  ..._config.cloud,
700
+ // Spread FIRST, then decide `enabled`, so a workspace that declares a `cloud:`
701
+ // block only to tune `requestTimeoutMs` does not disable its own sync. An
702
+ // EXPLICIT `enabled: false` is still honoured; `undefined` (not stated) means
703
+ // "a key resolved, so turn it on". (plan-2026-07-20-cloud-sync-timeout)
704
+ enabled: _config.cloud?.enabled ?? true,
372
705
  apiKey: resolvedKey.apiKey,
373
706
  endpoint: resolvedEndpoint
374
707
  };
@@ -588,9 +921,22 @@ var init_config = __esm({
588
921
  })).default([])
589
922
  }).optional();
590
923
  CloudConfigSchema = z2.object({
591
- enabled: z2.boolean().default(false),
924
+ // OPTIONAL, not `.default(false)`. A zod default is indistinguishable from an
925
+ // explicit value once parsed, so `.default(false)` meant that merely declaring a
926
+ // `cloud:` block (to set any OTHER key) silently produced `enabled: false`, which
927
+ // then overrode the `enabled: true` auto-enable below via object spread — turning
928
+ // cloud sync OFF as a side effect of tuning it. `undefined` now means "not stated",
929
+ // and the auto-enable preserves it. (plan-2026-07-20-cloud-sync-timeout)
930
+ enabled: z2.boolean().optional(),
592
931
  apiKey: z2.string().optional(),
593
932
  endpoint: z2.string().optional(),
933
+ // Per-request POST budget for the `/sync` ingest. cloud-sync.ts reads this
934
+ // (`(cloud as { requestTimeoutMs?: number }).requestTimeoutMs`) but it was ABSENT
935
+ // from this schema, and zod strips unknown keys — so the knob was unreachable from
936
+ // massu.config.yaml and the default could never be tuned. Measured 2026-07-20: a
937
+ // 423-observation payload takes ~9.2s against the live ingest. Capped at
938
+ // SYNC_DEADLINE_MS (20s) since the overall deadline clamps each attempt anyway.
939
+ requestTimeoutMs: z2.number().int().positive().max(2e4).optional(),
594
940
  sync: z2.object({
595
941
  memory: z2.boolean().default(true),
596
942
  analytics: z2.boolean().default(true),
@@ -848,27 +1194,26 @@ var init_memory_vector = __esm({
848
1194
  });
849
1195
 
850
1196
  // src/db-backup.ts
851
- import Database from "better-sqlite3";
852
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync2 } from "fs";
853
- import { resolve as resolve3, join, basename, dirname as dirname2 } from "path";
1197
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync3 } from "fs";
1198
+ import { resolve as resolve3, join as join2, basename, dirname as dirname4 } from "path";
854
1199
  import { homedir as homedir3 } from "os";
855
1200
  function dbBackupsRoot(home = homedir3()) {
856
1201
  return resolve3(home, ".massu", "db-backups");
857
1202
  }
858
1203
  function projectBackupDir(projectRoot, home = homedir3()) {
859
1204
  const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
860
- return join(dbBackupsRoot(home), slug);
1205
+ return join2(dbBackupsRoot(home), slug);
861
1206
  }
862
1207
  function backupStamp(nowMs) {
863
1208
  return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
864
1209
  }
865
1210
  function listDbBackups(projectRoot, home = homedir3()) {
866
1211
  const dir = projectBackupDir(projectRoot, home);
867
- if (!existsSync3(dir)) return [];
1212
+ if (!existsSync4(dir)) return [];
868
1213
  const out = [];
869
1214
  for (const f of readdirSync(dir)) {
870
1215
  if (!f.endsWith(".db")) continue;
871
- const p = join(dir, f);
1216
+ const p = join2(dir, f);
872
1217
  const st = statSync(p);
873
1218
  const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
874
1219
  out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
@@ -881,13 +1226,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
881
1226
  if (backups.length === 0) return false;
882
1227
  const newest = backups[0];
883
1228
  if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
884
- if (existsSync3(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
1229
+ if (existsSync4(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
885
1230
  return true;
886
1231
  }
887
1232
  function integrityOk(dbPath) {
888
1233
  let db = null;
889
1234
  try {
890
- db = new Database(dbPath, { readonly: true });
1235
+ db = openDatabase(dbPath, { readonly: true });
891
1236
  const rows = db.pragma("integrity_check");
892
1237
  return rows.length === 1 && rows[0].integrity_check === "ok";
893
1238
  } catch {
@@ -900,16 +1245,16 @@ function integrityOk(dbPath) {
900
1245
  }
901
1246
  }
902
1247
  function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
903
- if (!existsSync3(dbPath)) {
1248
+ if (!existsSync4(dbPath)) {
904
1249
  throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
905
1250
  }
906
1251
  const dir = projectBackupDir(projectRoot, home);
907
- mkdirSync2(dir, { recursive: true });
1252
+ mkdirSync4(dir, { recursive: true });
908
1253
  const name = basename(dbPath, ".db");
909
- const dest = join(dir, `${name}-${backupStamp(nowMs)}.db`);
1254
+ const dest = join2(dir, `${name}-${backupStamp(nowMs)}.db`);
910
1255
  let src = null;
911
1256
  try {
912
- src = new Database(dbPath, { readonly: true });
1257
+ src = openDatabase(dbPath, { readonly: true });
913
1258
  src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
914
1259
  } catch (err) {
915
1260
  throw new DbBackupError(
@@ -948,7 +1293,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
948
1293
  }
949
1294
  function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
950
1295
  try {
951
- if (!existsSync3(dbPath)) return null;
1296
+ if (!existsSync4(dbPath)) return null;
952
1297
  if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
953
1298
  return backupDb(projectRoot, dbPath, nowMs, home);
954
1299
  } catch (err) {
@@ -960,6 +1305,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
960
1305
  var init_db_backup = __esm({
961
1306
  "src/db-backup.ts"() {
962
1307
  "use strict";
1308
+ init_sqlite_loader();
963
1309
  DEFAULT_RETENTION = 5;
964
1310
  FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
965
1311
  DbBackupError = class extends Error {
@@ -1072,21 +1418,21 @@ var init_rule_candidate_store = __esm({
1072
1418
  });
1073
1419
 
1074
1420
  // src/hooks/lib/hook-failure-signal.ts
1075
- import { appendFileSync, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
1076
- import { join as join2, dirname as dirname3 } from "path";
1421
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync5 } from "fs";
1422
+ import { join as join3, dirname as dirname5 } from "path";
1077
1423
  function resolveFailureLogPath() {
1078
1424
  const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
1079
1425
  if (explicit) return explicit;
1080
1426
  let dir = process.cwd();
1081
1427
  for (let i = 0; i < 12; i++) {
1082
- if (existsSync4(join2(dir, ".massu")) || existsSync4(join2(dir, "massu.config.yaml"))) {
1083
- return join2(dir, ".massu", "hook-failures.jsonl");
1428
+ if (existsSync5(join3(dir, ".massu")) || existsSync5(join3(dir, "massu.config.yaml"))) {
1429
+ return join3(dir, ".massu", "hook-failures.jsonl");
1084
1430
  }
1085
- const parent = dirname3(dir);
1431
+ const parent = dirname5(dir);
1086
1432
  if (parent === dir) break;
1087
1433
  dir = parent;
1088
1434
  }
1089
- return join2(process.cwd(), ".massu", "hook-failures.jsonl");
1435
+ return join3(process.cwd(), ".massu", "hook-failures.jsonl");
1090
1436
  }
1091
1437
  function recordHookFailure(hook, error, context) {
1092
1438
  const record = {
@@ -1099,8 +1445,8 @@ function recordHookFailure(hook, error, context) {
1099
1445
  let wroteSomething = false;
1100
1446
  try {
1101
1447
  const path = resolveFailureLogPath();
1102
- mkdirSync3(dirname3(path), { recursive: true });
1103
- appendFileSync(path, JSON.stringify(record) + "\n", "utf-8");
1448
+ mkdirSync5(dirname5(path), { recursive: true });
1449
+ appendFileSync2(path, JSON.stringify(record) + "\n", "utf-8");
1104
1450
  wroteSomething = true;
1105
1451
  } catch {
1106
1452
  }
@@ -1143,9 +1489,9 @@ var init_hook_failure_signal = __esm({
1143
1489
  });
1144
1490
 
1145
1491
  // src/memory-embedder-tokenizer.ts
1146
- import { readFileSync as readFileSync3 } from "fs";
1492
+ import { readFileSync as readFileSync4 } from "fs";
1147
1493
  function loadVocab(vocabPath) {
1148
- const lines = readFileSync3(vocabPath, "utf-8").split("\n");
1494
+ const lines = readFileSync4(vocabPath, "utf-8").split("\n");
1149
1495
  const vocab = /* @__PURE__ */ new Map();
1150
1496
  for (let i = 0; i < lines.length; i++) {
1151
1497
  const tok = lines[i].replace(/\r$/, "");
@@ -1265,9 +1611,9 @@ var init_memory_embedder_tokenizer = __esm({
1265
1611
 
1266
1612
  // src/memory-embedder.ts
1267
1613
  import { fileURLToPath } from "url";
1268
- import { dirname as dirname4, join as join3 } from "path";
1269
- import { existsSync as existsSync5 } from "fs";
1270
- import { createRequire } from "module";
1614
+ import { dirname as dirname6, join as join4 } from "path";
1615
+ import { existsSync as existsSync6 } from "fs";
1616
+ import { createRequire as createRequire2 } from "module";
1271
1617
  function getActiveEmbedModel() {
1272
1618
  return _activeModel;
1273
1619
  }
@@ -1288,21 +1634,21 @@ function embeddingsDisabled() {
1288
1634
  }
1289
1635
  function resolveModelDir() {
1290
1636
  try {
1291
- let dir = dirname4(fileURLToPath(import.meta.url));
1637
+ let dir = dirname6(fileURLToPath(import.meta.url));
1292
1638
  let root = null;
1293
1639
  for (let i = 0; i < 8; i++) {
1294
- if (existsSync5(join3(dir, "package.json"))) {
1640
+ if (existsSync6(join4(dir, "package.json"))) {
1295
1641
  root = dir;
1296
1642
  break;
1297
1643
  }
1298
- const parent = dirname4(dir);
1644
+ const parent = dirname6(dir);
1299
1645
  if (parent === dir) break;
1300
1646
  dir = parent;
1301
1647
  }
1302
1648
  if (!root) return null;
1303
1649
  for (const rel of ["dist/embedder", "assets/embedder"]) {
1304
- const candidate = join3(root, rel);
1305
- if (existsSync5(join3(candidate, "model_quantized.onnx"))) return candidate;
1650
+ const candidate = join4(root, rel);
1651
+ if (existsSync6(join4(candidate, "model_quantized.onnx"))) return candidate;
1306
1652
  }
1307
1653
  return null;
1308
1654
  } catch {
@@ -1311,9 +1657,9 @@ function resolveModelDir() {
1311
1657
  }
1312
1658
  function resolveWasmDir() {
1313
1659
  try {
1314
- const req = createRequire(import.meta.url);
1315
- const main2 = req.resolve(ORT_PKG);
1316
- return dirname4(main2);
1660
+ const req2 = createRequire2(import.meta.url);
1661
+ const main2 = req2.resolve(ORT_PKG);
1662
+ return dirname6(main2);
1317
1663
  } catch {
1318
1664
  return null;
1319
1665
  }
@@ -1333,8 +1679,8 @@ async function getSession() {
1333
1679
  ort.env.wasm.proxy = false;
1334
1680
  ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
1335
1681
  _ortTensor = ort.Tensor;
1336
- const modelPath = join3(modelDir, "model_quantized.onnx");
1337
- const vocabPath = join3(modelDir, "vocab.txt");
1682
+ const modelPath = join4(modelDir, "model_quantized.onnx");
1683
+ const vocabPath = join4(modelDir, "vocab.txt");
1338
1684
  _vocab = loadVocab(vocabPath);
1339
1685
  const session = await ort.InferenceSession.create(modelPath, {
1340
1686
  executionProviders: ["wasm"],
@@ -1686,9 +2032,8 @@ __export(memory_db_exports, {
1686
2032
  upsertObservationEmbedding: () => upsertObservationEmbedding,
1687
2033
  usageWarmupElapsed: () => usageWarmupElapsed
1688
2034
  });
1689
- import Database2 from "better-sqlite3";
1690
- import { dirname as dirname5, basename as basename2 } from "path";
1691
- import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2035
+ import { dirname as dirname7, basename as basename2 } from "path";
2036
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
1692
2037
  function sanitizeFts5Query(raw) {
1693
2038
  const trimmed = raw.trim();
1694
2039
  if (!trimmed) return '""';
@@ -1704,12 +2049,12 @@ function sanitizeFts5QueryOr(raw) {
1704
2049
  }
1705
2050
  function getMemoryDb() {
1706
2051
  const dbPath = getResolvedPaths().memoryDbPath;
1707
- const dir = dirname5(dbPath);
1708
- if (!existsSync6(dir)) {
1709
- mkdirSync4(dir, { recursive: true });
2052
+ const dir = dirname7(dbPath);
2053
+ if (!existsSync7(dir)) {
2054
+ mkdirSync6(dir, { recursive: true });
1710
2055
  }
1711
- const preExisting = existsSync6(dbPath);
1712
- const db = new Database2(dbPath);
2056
+ const preExisting = existsSync7(dbPath);
2057
+ const db = openDatabase(dbPath);
1713
2058
  db.pragma("journal_mode = WAL");
1714
2059
  db.pragma("foreign_keys = ON");
1715
2060
  const onDisk = db.pragma("user_version", { simple: true });
@@ -3415,6 +3760,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
3415
3760
  var init_memory_db = __esm({
3416
3761
  "src/memory-db.ts"() {
3417
3762
  "use strict";
3763
+ init_sqlite_loader();
3418
3764
  init_config();
3419
3765
  init_memory_vector();
3420
3766
  init_db_backup();
@@ -3838,8 +4184,9 @@ function writeHookMessage(message) {
3838
4184
  }
3839
4185
 
3840
4186
  // src/hooks/memory-recall.ts
4187
+ init_sqlite_loader();
3841
4188
  init_hook_failure_signal();
3842
- import { existsSync as existsSync7 } from "fs";
4189
+ import { existsSync as existsSync8 } from "fs";
3843
4190
  var DEFAULTS = {
3844
4191
  enabled: true,
3845
4192
  maxTokens: 1200,
@@ -3909,10 +4256,9 @@ async function computeRecall(prompt, cfg, sessionId) {
3909
4256
  );
3910
4257
  memDb = getMemoryDb();
3911
4258
  const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
3912
- if (cfg.sources.includes("knowledge_chunk") && existsSync7(knowledgeDbPath)) {
4259
+ if (cfg.sources.includes("knowledge_chunk") && existsSync8(knowledgeDbPath)) {
3913
4260
  try {
3914
- const DatabaseCtor = (await import("better-sqlite3")).default;
3915
- knowledgeDb = new DatabaseCtor(knowledgeDbPath, { readonly: true });
4261
+ knowledgeDb = openDatabase(knowledgeDbPath, { readonly: true, selfHeal: false });
3916
4262
  } catch {
3917
4263
  knowledgeDb = null;
3918
4264
  }