@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.
@@ -366,9 +366,13 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
366
366
  const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
367
367
  if (resolvedKey.apiKey) {
368
368
  _config.cloud = {
369
- enabled: true,
370
369
  sync: { memory: true, analytics: true, audit: true },
371
370
  ..._config.cloud,
371
+ // Spread FIRST, then decide `enabled`, so a workspace that declares a `cloud:`
372
+ // block only to tune `requestTimeoutMs` does not disable its own sync. An
373
+ // EXPLICIT `enabled: false` is still honoured; `undefined` (not stated) means
374
+ // "a key resolved, so turn it on". (plan-2026-07-20-cloud-sync-timeout)
375
+ enabled: _config.cloud?.enabled ?? true,
372
376
  apiKey: resolvedKey.apiKey,
373
377
  endpoint: resolvedEndpoint
374
378
  };
@@ -588,9 +592,22 @@ var init_config = __esm({
588
592
  })).default([])
589
593
  }).optional();
590
594
  CloudConfigSchema = z2.object({
591
- enabled: z2.boolean().default(false),
595
+ // OPTIONAL, not `.default(false)`. A zod default is indistinguishable from an
596
+ // explicit value once parsed, so `.default(false)` meant that merely declaring a
597
+ // `cloud:` block (to set any OTHER key) silently produced `enabled: false`, which
598
+ // then overrode the `enabled: true` auto-enable below via object spread — turning
599
+ // cloud sync OFF as a side effect of tuning it. `undefined` now means "not stated",
600
+ // and the auto-enable preserves it. (plan-2026-07-20-cloud-sync-timeout)
601
+ enabled: z2.boolean().optional(),
592
602
  apiKey: z2.string().optional(),
593
603
  endpoint: z2.string().optional(),
604
+ // Per-request POST budget for the `/sync` ingest. cloud-sync.ts reads this
605
+ // (`(cloud as { requestTimeoutMs?: number }).requestTimeoutMs`) but it was ABSENT
606
+ // from this schema, and zod strips unknown keys — so the knob was unreachable from
607
+ // massu.config.yaml and the default could never be tuned. Measured 2026-07-20: a
608
+ // 423-observation payload takes ~9.2s against the live ingest. Capped at
609
+ // SYNC_DEADLINE_MS (20s) since the overall deadline clamps each attempt anyway.
610
+ requestTimeoutMs: z2.number().int().positive().max(2e4).optional(),
594
611
  sync: z2.object({
595
612
  memory: z2.boolean().default(true),
596
613
  analytics: z2.boolean().default(true),
@@ -807,6 +824,335 @@ var init_config = __esm({
807
824
  }
808
825
  });
809
826
 
827
+ // src/lib/fileLock.ts
828
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
829
+ import { dirname as dirname2 } from "path";
830
+ import * as lockfile from "proper-lockfile";
831
+ function readLockHolderPid(lockPath) {
832
+ try {
833
+ const raw = readFileSync3(`${lockPath}.pid`, "utf-8").trim();
834
+ const pid = Number.parseInt(raw, 10);
835
+ if (!Number.isFinite(pid) || pid <= 0) return null;
836
+ return pid;
837
+ } catch {
838
+ return null;
839
+ }
840
+ }
841
+ function busyWaitSync(ms) {
842
+ if (typeof SharedArrayBuffer === "undefined" || typeof Atomics === "undefined") {
843
+ throw new Error(
844
+ `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.`
845
+ );
846
+ }
847
+ const sab = new SharedArrayBuffer(4);
848
+ const view = new Int32Array(sab);
849
+ Atomics.wait(view, 0, 0, ms);
850
+ }
851
+ function withFileLockSync(lockPath, fn, opts = {}) {
852
+ mkdirSync2(dirname2(lockPath), { recursive: true });
853
+ const staleMs = opts.staleMs ?? 3e4;
854
+ const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
855
+ const pollIntervalMs = opts.pollIntervalMs ?? 100;
856
+ const now = opts.now ?? Date.now;
857
+ const sleep = opts.sleep ?? busyWaitSync;
858
+ const makeBusyError = opts.errorFactory ?? ((path, pid, retrySeconds, code) => new FileLockBusyError(path, pid, retrySeconds, code));
859
+ let release = null;
860
+ const deadline = now() + blockMs;
861
+ for (; ; ) {
862
+ try {
863
+ release = lockfile.lockSync(lockPath, {
864
+ stale: staleMs,
865
+ retries: 0,
866
+ realpath: false
867
+ });
868
+ try {
869
+ writeFileSync2(`${lockPath}.pid`, String(process.pid), "utf-8");
870
+ } catch {
871
+ }
872
+ break;
873
+ } catch (err) {
874
+ const e = err;
875
+ const code = e.code;
876
+ if (code !== "ELOCKED" && code !== "EBUSY") {
877
+ throw err;
878
+ }
879
+ if (now() >= deadline) {
880
+ const holderPid = readLockHolderPid(lockPath);
881
+ const remainingMs = Math.max(0, deadline - now());
882
+ const retryAfterSeconds = blockMs === 0 ? Math.round(staleMs / 1e3) : Math.round(remainingMs / 1e3);
883
+ throw makeBusyError(lockPath, holderPid, retryAfterSeconds, code);
884
+ }
885
+ sleep(pollIntervalMs);
886
+ }
887
+ }
888
+ try {
889
+ return fn();
890
+ } finally {
891
+ try {
892
+ if (release) release();
893
+ } catch {
894
+ }
895
+ try {
896
+ rmSync2(`${lockPath}.pid`, { force: true });
897
+ } catch {
898
+ }
899
+ }
900
+ }
901
+ var FileLockBusyError;
902
+ var init_fileLock = __esm({
903
+ "src/lib/fileLock.ts"() {
904
+ "use strict";
905
+ FileLockBusyError = class extends Error {
906
+ constructor(lockPath, holderPid, retryAfterSeconds, causeCode) {
907
+ const pidPart = holderPid != null ? `(PID=${holderPid})` : "(PID=unknown)";
908
+ super(`File lock at ${lockPath} held by another process ${pidPart} \u2014 try again in ${retryAfterSeconds}s`);
909
+ this.lockPath = lockPath;
910
+ this.holderPid = holderPid;
911
+ this.retryAfterSeconds = retryAfterSeconds;
912
+ this.causeCode = causeCode;
913
+ this.name = "FileLockBusyError";
914
+ }
915
+ lockPath;
916
+ holderPid;
917
+ retryAfterSeconds;
918
+ causeCode;
919
+ };
920
+ }
921
+ });
922
+
923
+ // src/lib/sqlite-loader.ts
924
+ import { createRequire } from "module";
925
+ import { spawnSync } from "child_process";
926
+ import { accessSync, appendFileSync, chmodSync as chmodSync2, constants as fsConstants, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
927
+ import { dirname as dirname3, join } from "path";
928
+ function isNativeAbiError(err) {
929
+ const code = err?.code;
930
+ if (code === "ERR_DLOPEN_FAILED") return true;
931
+ const msg = err instanceof Error ? err.message : String(err);
932
+ 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);
933
+ }
934
+ function loadBetterSqlite3(opts = {}) {
935
+ if (_testCtor) return _testCtor;
936
+ if (opts.fresh) bustNativeCache();
937
+ return req("better-sqlite3");
938
+ }
939
+ function bustNativeCache() {
940
+ try {
941
+ const cache = req.cache;
942
+ if (!cache) return;
943
+ for (const key of Object.keys(cache)) {
944
+ if (/better[-_]sqlite3|better_sqlite3\.node|[\\/]bindings[\\/]/.test(key)) {
945
+ delete cache[key];
946
+ }
947
+ }
948
+ } catch {
949
+ }
950
+ }
951
+ function openDatabase(dbPath, opts = {}) {
952
+ const { selfHeal: selfHealOpt = true, ...ctorOpts } = opts;
953
+ const selfHeal = selfHealOpt && process.env.MASSU_HOOK_RUNTIME !== "1";
954
+ const Ctor = loadBetterSqlite3();
955
+ try {
956
+ return new Ctor(dbPath, ctorOpts);
957
+ } catch (err) {
958
+ if (!isNativeAbiError(err)) {
959
+ throw err;
960
+ }
961
+ if (!selfHeal) {
962
+ recordHealEvent({ phase: "skipped", reason: "self-heal-disabled", abiTo: process.versions.modules });
963
+ throw new MemoryEngineUnusableError("abi-mismatch", detailOf(err));
964
+ }
965
+ const result = (_testHeal ?? attemptNativeHeal)(err);
966
+ if (!result.healed && !result.contended) {
967
+ throw new MemoryEngineUnusableError(result.reason ?? "heal-failed", result.detail ?? detailOf(err));
968
+ }
969
+ try {
970
+ const FreshCtor = loadBetterSqlite3({ fresh: true });
971
+ return new FreshCtor(dbPath, ctorOpts);
972
+ } catch (retryErr) {
973
+ throw new MemoryEngineUnusableError("heal-failed", detailOf(retryErr));
974
+ }
975
+ }
976
+ }
977
+ function attemptNativeHeal(err) {
978
+ const start = Date.now();
979
+ const abiTo = process.versions.modules;
980
+ const abiFrom = parseAbiFrom(err);
981
+ let pkgDir;
982
+ try {
983
+ pkgDir = dirname3(req.resolve("better-sqlite3/package.json"));
984
+ } catch {
985
+ recordHealEvent({ phase: "skipped", reason: "not-resolvable", abiFrom, abiTo });
986
+ return { healed: false, reason: "missing", abiFrom, abiTo, detail: "better-sqlite3 not resolvable" };
987
+ }
988
+ if (!isWritable(pkgDir)) {
989
+ recordHealEvent({ phase: "skipped", reason: "dir-not-writable", abiFrom, abiTo });
990
+ return { healed: false, reason: "heal-failed", abiFrom, abiTo, detail: `install dir not writable: ${pkgDir}` };
991
+ }
992
+ recordHealEvent({ phase: "attempt", reason: "abi-mismatch", abiFrom, abiTo });
993
+ try {
994
+ return withFileLockSync(
995
+ join(credentialsDir(), "native-heal.lock"),
996
+ () => runRebuild(pkgDir, abiFrom, abiTo, start),
997
+ { blockMs: 6e4, staleMs: 3e5 }
998
+ );
999
+ } catch (lockErr) {
1000
+ const detail = lockErr instanceof Error ? lockErr.message : String(lockErr);
1001
+ recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, detail: `lock: ${detail}` });
1002
+ return { healed: false, contended: true, reason: "heal-failed", abiFrom, abiTo, detail: `heal lock contended: ${detail}` };
1003
+ }
1004
+ }
1005
+ function runRebuild(pkgDir, abiFrom, abiTo, start) {
1006
+ try {
1007
+ const FreshCtor = loadBetterSqlite3({ fresh: true });
1008
+ new FreshCtor(":memory:").close();
1009
+ const res2 = {
1010
+ healed: true,
1011
+ abiFrom,
1012
+ abiTo,
1013
+ durationMs: Date.now() - start,
1014
+ detail: "already healed by a concurrent process"
1015
+ };
1016
+ recordHealEvent({ phase: "success", reason: "already-healed", abiFrom, abiTo, durationMs: res2.durationMs });
1017
+ return res2;
1018
+ } catch (probeErr) {
1019
+ if (!isNativeAbiError(probeErr)) {
1020
+ return { healed: false, reason: "heal-failed", abiFrom, abiTo, durationMs: Date.now() - start, detail: detailOf(probeErr) };
1021
+ }
1022
+ }
1023
+ const prebuildBin = resolveLocalBin(pkgDir, "prebuild-install");
1024
+ if (prebuildBin) {
1025
+ const r = spawnSync(process.execPath, [prebuildBin], {
1026
+ cwd: pkgDir,
1027
+ encoding: "utf-8",
1028
+ timeout: 12e4
1029
+ });
1030
+ if (r.status === 0) {
1031
+ const res2 = { healed: true, method: "prebuild-install", abiFrom, abiTo, durationMs: Date.now() - start };
1032
+ recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
1033
+ return res2;
1034
+ }
1035
+ }
1036
+ const gypBin = resolveNodeGypBin();
1037
+ if (gypBin && hasCompiler()) {
1038
+ const r = spawnSync(process.execPath, [gypBin, "rebuild", "--release"], {
1039
+ cwd: pkgDir,
1040
+ encoding: "utf-8",
1041
+ timeout: 3e5
1042
+ });
1043
+ if (r.status === 0) {
1044
+ const res2 = { healed: true, method: "node-gyp", abiFrom, abiTo, durationMs: Date.now() - start };
1045
+ recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
1046
+ return res2;
1047
+ }
1048
+ }
1049
+ const res = {
1050
+ healed: false,
1051
+ reason: "heal-failed",
1052
+ abiFrom,
1053
+ abiTo,
1054
+ durationMs: Date.now() - start,
1055
+ detail: prebuildBin ? "prebuild-install failed; node-gyp fallback unavailable or failed" : "no prebuild-install bin resolvable"
1056
+ };
1057
+ recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, durationMs: res.durationMs });
1058
+ return res;
1059
+ }
1060
+ function recordHealEvent(event) {
1061
+ try {
1062
+ const rec = {
1063
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1064
+ node: process.versions.node,
1065
+ platform: process.platform,
1066
+ arch: process.arch,
1067
+ ...event
1068
+ };
1069
+ const dir = credentialsDir();
1070
+ mkdirSync3(dir, { recursive: true });
1071
+ const file = join(dir, "native-heal-events.jsonl");
1072
+ appendFileSync(file, JSON.stringify(rec) + "\n", "utf-8");
1073
+ try {
1074
+ chmodSync2(file, 384);
1075
+ } catch {
1076
+ }
1077
+ } catch {
1078
+ }
1079
+ }
1080
+ function parseAbiFrom(err) {
1081
+ const msg = err instanceof Error ? err.message : String(err ?? "");
1082
+ const m = /NODE_MODULE_VERSION (\d+)/.exec(msg);
1083
+ if (m && /^\d+$/.test(m[1])) return m[1];
1084
+ return void 0;
1085
+ }
1086
+ function detailOf(err) {
1087
+ const msg = err instanceof Error ? err.message : String(err);
1088
+ return msg.slice(0, 300);
1089
+ }
1090
+ function isWritable(dir) {
1091
+ try {
1092
+ accessSync(dir, fsConstants.W_OK);
1093
+ return true;
1094
+ } catch {
1095
+ return false;
1096
+ }
1097
+ }
1098
+ function resolveLocalBin(pkgDir, name) {
1099
+ const candidates = [
1100
+ join(pkgDir, "node_modules", ".bin", name),
1101
+ // nested install
1102
+ join(pkgDir, "..", ".bin", name),
1103
+ // hoisted (node_modules/.bin)
1104
+ join(pkgDir, "..", "..", "node_modules", ".bin", name)
1105
+ // workspace hoist
1106
+ ];
1107
+ for (const c of candidates) {
1108
+ if (existsSync3(c)) return c;
1109
+ }
1110
+ return void 0;
1111
+ }
1112
+ function resolveNodeGypBin() {
1113
+ try {
1114
+ return req.resolve("node-gyp/bin/node-gyp.js");
1115
+ } catch {
1116
+ return void 0;
1117
+ }
1118
+ }
1119
+ function hasCompiler() {
1120
+ for (const cc of ["cc", "clang", "gcc"]) {
1121
+ try {
1122
+ const r = spawnSync(cc, ["--version"], { encoding: "utf-8", timeout: 5e3 });
1123
+ if (r.status === 0) return true;
1124
+ } catch {
1125
+ }
1126
+ }
1127
+ return false;
1128
+ }
1129
+ var req, NATIVE_DB_REMEDY, MemoryEngineUnusableError, _testCtor, _testHeal;
1130
+ var init_sqlite_loader = __esm({
1131
+ "src/lib/sqlite-loader.ts"() {
1132
+ "use strict";
1133
+ init_credentials();
1134
+ init_fileLock();
1135
+ req = createRequire(import.meta.url);
1136
+ NATIVE_DB_REMEDY = "Run 'massu heal' to rebuild the database engine for your Node version, then restart your MCP client / Claude Code.";
1137
+ MemoryEngineUnusableError = class extends Error {
1138
+ reason;
1139
+ remedy;
1140
+ detail;
1141
+ constructor(reason, detail) {
1142
+ super(
1143
+ `Massu memory engine is unusable (${reason}). ${NATIVE_DB_REMEDY}` + (detail ? ` [${detail}]` : "")
1144
+ );
1145
+ this.name = "MemoryEngineUnusableError";
1146
+ this.reason = reason;
1147
+ this.remedy = NATIVE_DB_REMEDY;
1148
+ this.detail = detail;
1149
+ }
1150
+ };
1151
+ _testCtor = null;
1152
+ _testHeal = null;
1153
+ }
1154
+ });
1155
+
810
1156
  // src/memory-vector.ts
811
1157
  function float32ToBlob(vec) {
812
1158
  return Buffer.from(new Uint8Array(vec.buffer, vec.byteOffset, vec.byteLength));
@@ -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 {
@@ -1051,21 +1397,21 @@ var init_rule_candidate_store = __esm({
1051
1397
  });
1052
1398
 
1053
1399
  // src/hooks/lib/hook-failure-signal.ts
1054
- import { appendFileSync, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
1055
- import { join as join2, dirname as dirname3 } from "path";
1400
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync5 } from "fs";
1401
+ import { join as join3, dirname as dirname5 } from "path";
1056
1402
  function resolveFailureLogPath() {
1057
1403
  const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
1058
1404
  if (explicit) return explicit;
1059
1405
  let dir = process.cwd();
1060
1406
  for (let i = 0; i < 12; i++) {
1061
- if (existsSync4(join2(dir, ".massu")) || existsSync4(join2(dir, "massu.config.yaml"))) {
1062
- return join2(dir, ".massu", "hook-failures.jsonl");
1407
+ if (existsSync5(join3(dir, ".massu")) || existsSync5(join3(dir, "massu.config.yaml"))) {
1408
+ return join3(dir, ".massu", "hook-failures.jsonl");
1063
1409
  }
1064
- const parent = dirname3(dir);
1410
+ const parent = dirname5(dir);
1065
1411
  if (parent === dir) break;
1066
1412
  dir = parent;
1067
1413
  }
1068
- return join2(process.cwd(), ".massu", "hook-failures.jsonl");
1414
+ return join3(process.cwd(), ".massu", "hook-failures.jsonl");
1069
1415
  }
1070
1416
  function recordHookFailure(hook, error, context) {
1071
1417
  const record = {
@@ -1078,8 +1424,8 @@ function recordHookFailure(hook, error, context) {
1078
1424
  let wroteSomething = false;
1079
1425
  try {
1080
1426
  const path = resolveFailureLogPath();
1081
- mkdirSync3(dirname3(path), { recursive: true });
1082
- appendFileSync(path, JSON.stringify(record) + "\n", "utf-8");
1427
+ mkdirSync5(dirname5(path), { recursive: true });
1428
+ appendFileSync2(path, JSON.stringify(record) + "\n", "utf-8");
1083
1429
  wroteSomething = true;
1084
1430
  } catch {
1085
1431
  }
@@ -1122,9 +1468,9 @@ var init_hook_failure_signal = __esm({
1122
1468
  });
1123
1469
 
1124
1470
  // src/memory-embedder-tokenizer.ts
1125
- import { readFileSync as readFileSync3 } from "fs";
1471
+ import { readFileSync as readFileSync4 } from "fs";
1126
1472
  function loadVocab(vocabPath) {
1127
- const lines = readFileSync3(vocabPath, "utf-8").split("\n");
1473
+ const lines = readFileSync4(vocabPath, "utf-8").split("\n");
1128
1474
  const vocab = /* @__PURE__ */ new Map();
1129
1475
  for (let i = 0; i < lines.length; i++) {
1130
1476
  const tok = lines[i].replace(/\r$/, "");
@@ -1244,9 +1590,9 @@ var init_memory_embedder_tokenizer = __esm({
1244
1590
 
1245
1591
  // src/memory-embedder.ts
1246
1592
  import { fileURLToPath } from "url";
1247
- import { dirname as dirname4, join as join3 } from "path";
1248
- import { existsSync as existsSync5 } from "fs";
1249
- import { createRequire } from "module";
1593
+ import { dirname as dirname6, join as join4 } from "path";
1594
+ import { existsSync as existsSync6 } from "fs";
1595
+ import { createRequire as createRequire2 } from "module";
1250
1596
  function getActiveEmbedModel() {
1251
1597
  return _activeModel;
1252
1598
  }
@@ -1267,21 +1613,21 @@ function embeddingsDisabled() {
1267
1613
  }
1268
1614
  function resolveModelDir() {
1269
1615
  try {
1270
- let dir = dirname4(fileURLToPath(import.meta.url));
1616
+ let dir = dirname6(fileURLToPath(import.meta.url));
1271
1617
  let root = null;
1272
1618
  for (let i = 0; i < 8; i++) {
1273
- if (existsSync5(join3(dir, "package.json"))) {
1619
+ if (existsSync6(join4(dir, "package.json"))) {
1274
1620
  root = dir;
1275
1621
  break;
1276
1622
  }
1277
- const parent = dirname4(dir);
1623
+ const parent = dirname6(dir);
1278
1624
  if (parent === dir) break;
1279
1625
  dir = parent;
1280
1626
  }
1281
1627
  if (!root) return null;
1282
1628
  for (const rel of ["dist/embedder", "assets/embedder"]) {
1283
- const candidate = join3(root, rel);
1284
- if (existsSync5(join3(candidate, "model_quantized.onnx"))) return candidate;
1629
+ const candidate = join4(root, rel);
1630
+ if (existsSync6(join4(candidate, "model_quantized.onnx"))) return candidate;
1285
1631
  }
1286
1632
  return null;
1287
1633
  } catch {
@@ -1290,9 +1636,9 @@ function resolveModelDir() {
1290
1636
  }
1291
1637
  function resolveWasmDir() {
1292
1638
  try {
1293
- const req = createRequire(import.meta.url);
1294
- const main2 = req.resolve(ORT_PKG);
1295
- return dirname4(main2);
1639
+ const req2 = createRequire2(import.meta.url);
1640
+ const main2 = req2.resolve(ORT_PKG);
1641
+ return dirname6(main2);
1296
1642
  } catch {
1297
1643
  return null;
1298
1644
  }
@@ -1312,8 +1658,8 @@ async function getSession() {
1312
1658
  ort.env.wasm.proxy = false;
1313
1659
  ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
1314
1660
  _ortTensor = ort.Tensor;
1315
- const modelPath = join3(modelDir, "model_quantized.onnx");
1316
- const vocabPath = join3(modelDir, "vocab.txt");
1661
+ const modelPath = join4(modelDir, "model_quantized.onnx");
1662
+ const vocabPath = join4(modelDir, "vocab.txt");
1317
1663
  _vocab = loadVocab(vocabPath);
1318
1664
  const session = await ort.InferenceSession.create(modelPath, {
1319
1665
  executionProviders: ["wasm"],
@@ -1650,9 +1996,8 @@ __export(memory_db_exports, {
1650
1996
  upsertObservationEmbedding: () => upsertObservationEmbedding,
1651
1997
  usageWarmupElapsed: () => usageWarmupElapsed
1652
1998
  });
1653
- import Database2 from "better-sqlite3";
1654
- import { dirname as dirname5, basename as basename2 } from "path";
1655
- import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
1999
+ import { dirname as dirname7, basename as basename2 } from "path";
2000
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
1656
2001
  function sanitizeFts5Query(raw) {
1657
2002
  const trimmed = raw.trim();
1658
2003
  if (!trimmed) return '""';
@@ -1668,12 +2013,12 @@ function sanitizeFts5QueryOr(raw) {
1668
2013
  }
1669
2014
  function getMemoryDb() {
1670
2015
  const dbPath = getResolvedPaths().memoryDbPath;
1671
- const dir = dirname5(dbPath);
1672
- if (!existsSync6(dir)) {
1673
- mkdirSync4(dir, { recursive: true });
2016
+ const dir = dirname7(dbPath);
2017
+ if (!existsSync7(dir)) {
2018
+ mkdirSync6(dir, { recursive: true });
1674
2019
  }
1675
- const preExisting = existsSync6(dbPath);
1676
- const db = new Database2(dbPath);
2020
+ const preExisting = existsSync7(dbPath);
2021
+ const db = openDatabase(dbPath);
1677
2022
  db.pragma("journal_mode = WAL");
1678
2023
  db.pragma("foreign_keys = ON");
1679
2024
  const onDisk = db.pragma("user_version", { simple: true });
@@ -3379,6 +3724,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
3379
3724
  var init_memory_db = __esm({
3380
3725
  "src/memory-db.ts"() {
3381
3726
  "use strict";
3727
+ init_sqlite_loader();
3382
3728
  init_config();
3383
3729
  init_memory_vector();
3384
3730
  init_db_backup();
@@ -3401,7 +3747,7 @@ var init_memory_db = __esm({
3401
3747
  // src/hooks/incident-pipeline.ts
3402
3748
  init_config();
3403
3749
  init_memory_db();
3404
- import { existsSync as existsSync7, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
3750
+ import { existsSync as existsSync8, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "fs";
3405
3751
  import { basename as basename3, resolve as resolve5 } from "path";
3406
3752
 
3407
3753
  // src/hooks/lib/write-hook-message.ts
@@ -3433,17 +3779,17 @@ async function main() {
3433
3779
  process.exit(0);
3434
3780
  return;
3435
3781
  }
3436
- if (!existsSync7(filePath)) {
3782
+ if (!existsSync8(filePath)) {
3437
3783
  process.exit(0);
3438
3784
  return;
3439
3785
  }
3440
- const content = readFileSync4(filePath, "utf-8");
3786
+ const content = readFileSync5(filePath, "utf-8");
3441
3787
  const titleMatch = content.match(/^#\s+(.+)/m);
3442
3788
  const title = titleMatch?.[1] ?? basename3(filePath, ".md");
3443
3789
  const slug = basename3(filePath, ".md").replace(/^\d{4}-\d{2}-\d{2}-?/, "").toLowerCase();
3444
3790
  const memoryDirAbs = resolve5(root, memoryDir);
3445
3791
  let hasExistingRule = false;
3446
- if (existsSync7(memoryDirAbs)) {
3792
+ if (existsSync8(memoryDirAbs)) {
3447
3793
  const ruleFiles = readdirSync2(memoryDirAbs).filter((f) => f.startsWith("feedback_"));
3448
3794
  for (const ruleFile of ruleFiles) {
3449
3795
  if (ruleFile.toLowerCase().includes(slug.slice(0, 20))) {