@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),
@@ -827,27 +1173,26 @@ var init_memory_vector = __esm({
827
1173
  });
828
1174
 
829
1175
  // src/db-backup.ts
830
- import Database from "better-sqlite3";
831
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync2 } from "fs";
832
- import { resolve as resolve3, join, basename, dirname as dirname2 } from "path";
1176
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync3 } from "fs";
1177
+ import { resolve as resolve3, join as join2, basename, dirname as dirname4 } from "path";
833
1178
  import { homedir as homedir3 } from "os";
834
1179
  function dbBackupsRoot(home = homedir3()) {
835
1180
  return resolve3(home, ".massu", "db-backups");
836
1181
  }
837
1182
  function projectBackupDir(projectRoot, home = homedir3()) {
838
1183
  const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
839
- return join(dbBackupsRoot(home), slug);
1184
+ return join2(dbBackupsRoot(home), slug);
840
1185
  }
841
1186
  function backupStamp(nowMs) {
842
1187
  return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
843
1188
  }
844
1189
  function listDbBackups(projectRoot, home = homedir3()) {
845
1190
  const dir = projectBackupDir(projectRoot, home);
846
- if (!existsSync3(dir)) return [];
1191
+ if (!existsSync4(dir)) return [];
847
1192
  const out = [];
848
1193
  for (const f of readdirSync(dir)) {
849
1194
  if (!f.endsWith(".db")) continue;
850
- const p = join(dir, f);
1195
+ const p = join2(dir, f);
851
1196
  const st = statSync(p);
852
1197
  const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
853
1198
  out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
@@ -860,13 +1205,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
860
1205
  if (backups.length === 0) return false;
861
1206
  const newest = backups[0];
862
1207
  if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
863
- if (existsSync3(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
1208
+ if (existsSync4(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
864
1209
  return true;
865
1210
  }
866
1211
  function integrityOk(dbPath) {
867
1212
  let db = null;
868
1213
  try {
869
- db = new Database(dbPath, { readonly: true });
1214
+ db = openDatabase(dbPath, { readonly: true });
870
1215
  const rows = db.pragma("integrity_check");
871
1216
  return rows.length === 1 && rows[0].integrity_check === "ok";
872
1217
  } catch {
@@ -879,16 +1224,16 @@ function integrityOk(dbPath) {
879
1224
  }
880
1225
  }
881
1226
  function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
882
- if (!existsSync3(dbPath)) {
1227
+ if (!existsSync4(dbPath)) {
883
1228
  throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
884
1229
  }
885
1230
  const dir = projectBackupDir(projectRoot, home);
886
- mkdirSync2(dir, { recursive: true });
1231
+ mkdirSync4(dir, { recursive: true });
887
1232
  const name = basename(dbPath, ".db");
888
- const dest = join(dir, `${name}-${backupStamp(nowMs)}.db`);
1233
+ const dest = join2(dir, `${name}-${backupStamp(nowMs)}.db`);
889
1234
  let src = null;
890
1235
  try {
891
- src = new Database(dbPath, { readonly: true });
1236
+ src = openDatabase(dbPath, { readonly: true });
892
1237
  src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
893
1238
  } catch (err) {
894
1239
  throw new DbBackupError(
@@ -927,7 +1272,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
927
1272
  }
928
1273
  function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
929
1274
  try {
930
- if (!existsSync3(dbPath)) return null;
1275
+ if (!existsSync4(dbPath)) return null;
931
1276
  if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
932
1277
  return backupDb(projectRoot, dbPath, nowMs, home);
933
1278
  } catch (err) {
@@ -939,6 +1284,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
939
1284
  var init_db_backup = __esm({
940
1285
  "src/db-backup.ts"() {
941
1286
  "use strict";
1287
+ init_sqlite_loader();
942
1288
  DEFAULT_RETENTION = 5;
943
1289
  FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
944
1290
  DbBackupError = class extends Error {
@@ -1026,8 +1372,8 @@ var init_rule_delivery = __esm({
1026
1372
  });
1027
1373
 
1028
1374
  // src/rule-candidate-store.ts
1029
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "node:fs";
1030
- import { join as join2 } from "node:path";
1375
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "node:fs";
1376
+ import { join as join3 } from "node:path";
1031
1377
  function ensureRuleCandidatesTable(db) {
1032
1378
  db.exec(`
1033
1379
  CREATE TABLE IF NOT EXISTS rule_candidates (
@@ -1047,7 +1393,7 @@ function ensureRuleCandidatesTable(db) {
1047
1393
  `);
1048
1394
  }
1049
1395
  function sidecarPath(projectRoot, promptHash) {
1050
- return join2(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
1396
+ return join3(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
1051
1397
  }
1052
1398
  function upsertCandidate(db, projectRoot, candidate, opts = {}) {
1053
1399
  ensureRuleCandidatesTable(db);
@@ -1062,11 +1408,11 @@ function upsertCandidate(db, projectRoot, candidate, opts = {}) {
1062
1408
  origin = excluded.origin,
1063
1409
  updated_at = datetime('now')
1064
1410
  `).run(candidate.prompt_hash, origin, score, JSON.stringify(candidate));
1065
- const dir = join2(projectRoot, ".massu", "rule-candidates");
1066
- mkdirSync3(dir, { recursive: true });
1411
+ const dir = join3(projectRoot, ".massu", "rule-candidates");
1412
+ mkdirSync5(dir, { recursive: true });
1067
1413
  const path = sidecarPath(projectRoot, candidate.prompt_hash);
1068
- if (!existsSync4(path)) {
1069
- writeFileSync2(path, JSON.stringify(candidate, null, 2), "utf-8");
1414
+ if (!existsSync5(path)) {
1415
+ writeFileSync3(path, JSON.stringify(candidate, null, 2), "utf-8");
1070
1416
  }
1071
1417
  }
1072
1418
  var init_rule_candidate_store = __esm({
@@ -1076,21 +1422,21 @@ var init_rule_candidate_store = __esm({
1076
1422
  });
1077
1423
 
1078
1424
  // src/hooks/lib/hook-failure-signal.ts
1079
- import { appendFileSync, mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
1080
- import { join as join3, dirname as dirname3 } from "path";
1425
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6, existsSync as existsSync6 } from "fs";
1426
+ import { join as join4, dirname as dirname5 } from "path";
1081
1427
  function resolveFailureLogPath() {
1082
1428
  const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
1083
1429
  if (explicit) return explicit;
1084
1430
  let dir = process.cwd();
1085
1431
  for (let i = 0; i < 12; i++) {
1086
- if (existsSync5(join3(dir, ".massu")) || existsSync5(join3(dir, "massu.config.yaml"))) {
1087
- return join3(dir, ".massu", "hook-failures.jsonl");
1432
+ if (existsSync6(join4(dir, ".massu")) || existsSync6(join4(dir, "massu.config.yaml"))) {
1433
+ return join4(dir, ".massu", "hook-failures.jsonl");
1088
1434
  }
1089
- const parent = dirname3(dir);
1435
+ const parent = dirname5(dir);
1090
1436
  if (parent === dir) break;
1091
1437
  dir = parent;
1092
1438
  }
1093
- return join3(process.cwd(), ".massu", "hook-failures.jsonl");
1439
+ return join4(process.cwd(), ".massu", "hook-failures.jsonl");
1094
1440
  }
1095
1441
  function recordHookFailure(hook, error, context) {
1096
1442
  const record = {
@@ -1103,8 +1449,8 @@ function recordHookFailure(hook, error, context) {
1103
1449
  let wroteSomething = false;
1104
1450
  try {
1105
1451
  const path = resolveFailureLogPath();
1106
- mkdirSync4(dirname3(path), { recursive: true });
1107
- appendFileSync(path, JSON.stringify(record) + "\n", "utf-8");
1452
+ mkdirSync6(dirname5(path), { recursive: true });
1453
+ appendFileSync2(path, JSON.stringify(record) + "\n", "utf-8");
1108
1454
  wroteSomething = true;
1109
1455
  } catch {
1110
1456
  }
@@ -1147,9 +1493,9 @@ var init_hook_failure_signal = __esm({
1147
1493
  });
1148
1494
 
1149
1495
  // src/memory-embedder-tokenizer.ts
1150
- import { readFileSync as readFileSync4 } from "fs";
1496
+ import { readFileSync as readFileSync5 } from "fs";
1151
1497
  function loadVocab(vocabPath) {
1152
- const lines = readFileSync4(vocabPath, "utf-8").split("\n");
1498
+ const lines = readFileSync5(vocabPath, "utf-8").split("\n");
1153
1499
  const vocab = /* @__PURE__ */ new Map();
1154
1500
  for (let i = 0; i < lines.length; i++) {
1155
1501
  const tok = lines[i].replace(/\r$/, "");
@@ -1269,9 +1615,9 @@ var init_memory_embedder_tokenizer = __esm({
1269
1615
 
1270
1616
  // src/memory-embedder.ts
1271
1617
  import { fileURLToPath } from "url";
1272
- import { dirname as dirname4, join as join4 } from "path";
1273
- import { existsSync as existsSync6 } from "fs";
1274
- import { createRequire } from "module";
1618
+ import { dirname as dirname6, join as join5 } from "path";
1619
+ import { existsSync as existsSync7 } from "fs";
1620
+ import { createRequire as createRequire2 } from "module";
1275
1621
  function getActiveEmbedModel() {
1276
1622
  return _activeModel;
1277
1623
  }
@@ -1292,21 +1638,21 @@ function embeddingsDisabled() {
1292
1638
  }
1293
1639
  function resolveModelDir() {
1294
1640
  try {
1295
- let dir = dirname4(fileURLToPath(import.meta.url));
1641
+ let dir = dirname6(fileURLToPath(import.meta.url));
1296
1642
  let root = null;
1297
1643
  for (let i = 0; i < 8; i++) {
1298
- if (existsSync6(join4(dir, "package.json"))) {
1644
+ if (existsSync7(join5(dir, "package.json"))) {
1299
1645
  root = dir;
1300
1646
  break;
1301
1647
  }
1302
- const parent = dirname4(dir);
1648
+ const parent = dirname6(dir);
1303
1649
  if (parent === dir) break;
1304
1650
  dir = parent;
1305
1651
  }
1306
1652
  if (!root) return null;
1307
1653
  for (const rel of ["dist/embedder", "assets/embedder"]) {
1308
- const candidate = join4(root, rel);
1309
- if (existsSync6(join4(candidate, "model_quantized.onnx"))) return candidate;
1654
+ const candidate = join5(root, rel);
1655
+ if (existsSync7(join5(candidate, "model_quantized.onnx"))) return candidate;
1310
1656
  }
1311
1657
  return null;
1312
1658
  } catch {
@@ -1315,9 +1661,9 @@ function resolveModelDir() {
1315
1661
  }
1316
1662
  function resolveWasmDir() {
1317
1663
  try {
1318
- const req = createRequire(import.meta.url);
1319
- const main2 = req.resolve(ORT_PKG);
1320
- return dirname4(main2);
1664
+ const req2 = createRequire2(import.meta.url);
1665
+ const main2 = req2.resolve(ORT_PKG);
1666
+ return dirname6(main2);
1321
1667
  } catch {
1322
1668
  return null;
1323
1669
  }
@@ -1337,8 +1683,8 @@ async function getSession() {
1337
1683
  ort.env.wasm.proxy = false;
1338
1684
  ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
1339
1685
  _ortTensor = ort.Tensor;
1340
- const modelPath = join4(modelDir, "model_quantized.onnx");
1341
- const vocabPath = join4(modelDir, "vocab.txt");
1686
+ const modelPath = join5(modelDir, "model_quantized.onnx");
1687
+ const vocabPath = join5(modelDir, "vocab.txt");
1342
1688
  _vocab = loadVocab(vocabPath);
1343
1689
  const session = await ort.InferenceSession.create(modelPath, {
1344
1690
  executionProviders: ["wasm"],
@@ -1675,9 +2021,8 @@ __export(memory_db_exports, {
1675
2021
  upsertObservationEmbedding: () => upsertObservationEmbedding,
1676
2022
  usageWarmupElapsed: () => usageWarmupElapsed
1677
2023
  });
1678
- import Database2 from "better-sqlite3";
1679
- import { dirname as dirname5, basename as basename2 } from "path";
1680
- import { existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
2024
+ import { dirname as dirname7, basename as basename2 } from "path";
2025
+ import { existsSync as existsSync8, mkdirSync as mkdirSync7 } from "fs";
1681
2026
  function sanitizeFts5Query(raw) {
1682
2027
  const trimmed = raw.trim();
1683
2028
  if (!trimmed) return '""';
@@ -1693,12 +2038,12 @@ function sanitizeFts5QueryOr(raw) {
1693
2038
  }
1694
2039
  function getMemoryDb() {
1695
2040
  const dbPath = getResolvedPaths().memoryDbPath;
1696
- const dir = dirname5(dbPath);
1697
- if (!existsSync7(dir)) {
1698
- mkdirSync5(dir, { recursive: true });
2041
+ const dir = dirname7(dbPath);
2042
+ if (!existsSync8(dir)) {
2043
+ mkdirSync7(dir, { recursive: true });
1699
2044
  }
1700
- const preExisting = existsSync7(dbPath);
1701
- const db = new Database2(dbPath);
2045
+ const preExisting = existsSync8(dbPath);
2046
+ const db = openDatabase(dbPath);
1702
2047
  db.pragma("journal_mode = WAL");
1703
2048
  db.pragma("foreign_keys = ON");
1704
2049
  const onDisk = db.pragma("user_version", { simple: true });
@@ -3404,6 +3749,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
3404
3749
  var init_memory_db = __esm({
3405
3750
  "src/memory-db.ts"() {
3406
3751
  "use strict";
3752
+ init_sqlite_loader();
3407
3753
  init_config();
3408
3754
  init_memory_vector();
3409
3755
  init_db_backup();
@@ -3425,9 +3771,10 @@ var init_memory_db = __esm({
3425
3771
 
3426
3772
  // src/hooks/user-prompt.ts
3427
3773
  init_memory_db();
3774
+ init_sqlite_loader();
3428
3775
  init_config();
3429
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, writeFileSync as writeFileSync3, readFileSync as readFileSync5, readdirSync as readdirSync3, openSync, fstatSync, readSync, closeSync } from "fs";
3430
- import { join as join5 } from "path";
3776
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync4, readFileSync as readFileSync6, readdirSync as readdirSync3, openSync, fstatSync, readSync, closeSync } from "fs";
3777
+ import { join as join6 } from "path";
3431
3778
 
3432
3779
  // src/rule-candidate-detector.ts
3433
3780
  var CORRECTION_DISMISSAL_PATTERN = /\b(nevermind|never\s+mind|forget\s+it|ignore\s+(that|it)|actually\s+you('?re|\s+were)\s+right|on\s+second\s+thought|no\s+wait|scratch\s+that|disregard|abandon\s+(that|it))\b/i;
@@ -3768,9 +4115,8 @@ async function main() {
3768
4115
  const fileRefs = extractFileReferences(prompt);
3769
4116
  if (fileRefs.length > 0) {
3770
4117
  const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
3771
- if (knowledgeDbPath && existsSync8(knowledgeDbPath)) {
3772
- const BetterSqlite3Ctor = (await import("better-sqlite3")).default;
3773
- const kdb = new BetterSqlite3Ctor(knowledgeDbPath, { readonly: true });
4118
+ if (knowledgeDbPath && existsSync9(knowledgeDbPath)) {
4119
+ const kdb = openDatabase(knowledgeDbPath, { readonly: true, selfHeal: false });
3774
4120
  try {
3775
4121
  const placeholders = fileRefs.map(() => "?").join(",");
3776
4122
  const matches = kdb.prepare(
@@ -3822,14 +4168,14 @@ async function main() {
3822
4168
  category: categorizePrompt(prompt),
3823
4169
  blacklist
3824
4170
  });
3825
- const candidateDir = join5(hookInput.cwd, ".massu", "rule-candidates");
4171
+ const candidateDir = join6(hookInput.cwd, ".massu", "rule-candidates");
3826
4172
  if (scoreResult.emitCandidate) {
3827
4173
  const cachedTier = getCachedTierReadOnly(db);
3828
4174
  if (entitledForAutoLearning(cachedTier)) {
3829
- mkdirSync6(candidateDir, { recursive: true });
4175
+ mkdirSync8(candidateDir, { recursive: true });
3830
4176
  const promptHash = hashPrompt(prompt);
3831
- const candidatePath = join5(candidateDir, `${promptHash}.json`);
3832
- if (!existsSync8(candidatePath)) {
4177
+ const candidatePath = join6(candidateDir, `${promptHash}.json`);
4178
+ if (!existsSync9(candidatePath)) {
3833
4179
  upsertCandidate(db, hookInput.cwd, {
3834
4180
  prompt,
3835
4181
  prompt_hash: promptHash,
@@ -3853,28 +4199,28 @@ async function main() {
3853
4199
  }
3854
4200
  }
3855
4201
  } else {
3856
- const nudgePath = join5(candidateDir, ".last-tier-nudge");
3857
- if (!existsSync8(nudgePath)) {
3858
- mkdirSync6(candidateDir, { recursive: true });
4202
+ const nudgePath = join6(candidateDir, ".last-tier-nudge");
4203
+ if (!existsSync9(nudgePath)) {
4204
+ mkdirSync8(candidateDir, { recursive: true });
3859
4205
  process.stderr.write(
3860
4206
  `
3861
4207
  [RULE CANDIDATE] ${autoLearningUpgradeMessage(cachedTier)}
3862
4208
 
3863
4209
  `
3864
4210
  );
3865
- writeFileSync3(nudgePath, (/* @__PURE__ */ new Date()).toISOString());
4211
+ writeFileSync4(nudgePath, (/* @__PURE__ */ new Date()).toISOString());
3866
4212
  }
3867
4213
  }
3868
4214
  }
3869
- if (existsSync8(candidateDir)) {
4215
+ if (existsSync9(candidateDir)) {
3870
4216
  const candidates = readdirSync3(candidateDir).filter(
3871
4217
  (f) => f.endsWith(".json") && !f.startsWith(".")
3872
4218
  );
3873
4219
  const candidateCount = candidates.length;
3874
- const surfacedPath = join5(candidateDir, ".last-surfaced");
4220
+ const surfacedPath = join6(candidateDir, ".last-surfaced");
3875
4221
  let lastSurfaced = 0;
3876
- if (existsSync8(surfacedPath)) {
3877
- const raw = readFileSync5(surfacedPath, "utf-8").trim();
4222
+ if (existsSync9(surfacedPath)) {
4223
+ const raw = readFileSync6(surfacedPath, "utf-8").trim();
3878
4224
  const parsed = parseInt(raw, 10);
3879
4225
  if (!Number.isNaN(parsed)) lastSurfaced = parsed;
3880
4226
  }
@@ -3885,17 +4231,17 @@ async function main() {
3885
4231
 
3886
4232
  `
3887
4233
  );
3888
- writeFileSync3(surfacedPath, String(candidateCount));
4234
+ writeFileSync4(surfacedPath, String(candidateCount));
3889
4235
  }
3890
4236
  }
3891
4237
  } catch (candidateErr) {
3892
4238
  try {
3893
- const dir = join5(hookInput.cwd, ".massu", "rule-candidates");
3894
- if (!existsSync8(dir)) mkdirSync6(dir, { recursive: true });
3895
- const logPath = join5(dir, ".detector-failures.jsonl");
3896
- const pre = existsSync8(logPath) ? readFileSync5(logPath, "utf-8") : "";
4239
+ const dir = join6(hookInput.cwd, ".massu", "rule-candidates");
4240
+ if (!existsSync9(dir)) mkdirSync8(dir, { recursive: true });
4241
+ const logPath = join6(dir, ".detector-failures.jsonl");
4242
+ const pre = existsSync9(logPath) ? readFileSync6(logPath, "utf-8") : "";
3897
4243
  const sep = pre && !pre.endsWith("\n") ? "\n" : "";
3898
- writeFileSync3(logPath, pre + sep + JSON.stringify({
4244
+ writeFileSync4(logPath, pre + sep + JSON.stringify({
3899
4245
  session_id,
3900
4246
  // Security review (plan-2026-05-27): log the prompt HASH, never a
3901
4247
  // raw prompt excerpt — the failure log must not persist PII/secrets.
@@ -3916,7 +4262,7 @@ async function main() {
3916
4262
  }
3917
4263
  function detectPriorAssistantTurn(transcriptPath) {
3918
4264
  try {
3919
- if (!transcriptPath || !existsSync8(transcriptPath)) {
4265
+ if (!transcriptPath || !existsSync9(transcriptPath)) {
3920
4266
  return { hadEditOrWrite: false, files: [] };
3921
4267
  }
3922
4268
  if (!transcriptPath.includes("/.claude/projects/")) {
@@ -3992,8 +4338,8 @@ function extractFileReferences(prompt) {
3992
4338
  }
3993
4339
  async function getGitBranch() {
3994
4340
  try {
3995
- const { spawnSync } = await import("child_process");
3996
- const result = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
4341
+ const { spawnSync: spawnSync2 } = await import("child_process");
4342
+ const result = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
3997
4343
  encoding: "utf-8",
3998
4344
  timeout: 5e3
3999
4345
  });