@massu/core 1.16.1 → 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.
@@ -32,16 +32,6 @@ var __copyProps = (to, from, except, desc) => {
32
32
  };
33
33
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
34
34
 
35
- // src/lib/memory-path.ts
36
- function encodeMemoryDirName(projectRoot) {
37
- return projectRoot.replace(/\//g, "-");
38
- }
39
- var init_memory_path = __esm({
40
- "src/lib/memory-path.ts"() {
41
- "use strict";
42
- }
43
- });
44
-
45
35
  // src/credentials.ts
46
36
  import { homedir } from "os";
47
37
  import { resolve } from "path";
@@ -110,6 +100,345 @@ var init_credentials = __esm({
110
100
  }
111
101
  });
112
102
 
103
+ // src/lib/fileLock.ts
104
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
105
+ import { dirname } from "path";
106
+ import * as lockfile from "proper-lockfile";
107
+ function readLockHolderPid(lockPath) {
108
+ try {
109
+ const raw = readFileSync2(`${lockPath}.pid`, "utf-8").trim();
110
+ const pid = Number.parseInt(raw, 10);
111
+ if (!Number.isFinite(pid) || pid <= 0) return null;
112
+ return pid;
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+ function busyWaitSync(ms) {
118
+ if (typeof SharedArrayBuffer === "undefined" || typeof Atomics === "undefined") {
119
+ throw new Error(
120
+ `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.`
121
+ );
122
+ }
123
+ const sab = new SharedArrayBuffer(4);
124
+ const view = new Int32Array(sab);
125
+ Atomics.wait(view, 0, 0, ms);
126
+ }
127
+ function withFileLockSync(lockPath, fn, opts = {}) {
128
+ mkdirSync2(dirname(lockPath), { recursive: true });
129
+ const staleMs = opts.staleMs ?? 3e4;
130
+ const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
131
+ const pollIntervalMs = opts.pollIntervalMs ?? 100;
132
+ const now = opts.now ?? Date.now;
133
+ const sleep = opts.sleep ?? busyWaitSync;
134
+ const makeBusyError = opts.errorFactory ?? ((path, pid, retrySeconds, code) => new FileLockBusyError(path, pid, retrySeconds, code));
135
+ let release = null;
136
+ const deadline = now() + blockMs;
137
+ for (; ; ) {
138
+ try {
139
+ release = lockfile.lockSync(lockPath, {
140
+ stale: staleMs,
141
+ retries: 0,
142
+ realpath: false
143
+ });
144
+ try {
145
+ writeFileSync2(`${lockPath}.pid`, String(process.pid), "utf-8");
146
+ } catch {
147
+ }
148
+ break;
149
+ } catch (err) {
150
+ const e = err;
151
+ const code = e.code;
152
+ if (code !== "ELOCKED" && code !== "EBUSY") {
153
+ throw err;
154
+ }
155
+ if (now() >= deadline) {
156
+ const holderPid = readLockHolderPid(lockPath);
157
+ const remainingMs = Math.max(0, deadline - now());
158
+ const retryAfterSeconds = blockMs === 0 ? Math.round(staleMs / 1e3) : Math.round(remainingMs / 1e3);
159
+ throw makeBusyError(lockPath, holderPid, retryAfterSeconds, code);
160
+ }
161
+ sleep(pollIntervalMs);
162
+ }
163
+ }
164
+ try {
165
+ return fn();
166
+ } finally {
167
+ try {
168
+ if (release) release();
169
+ } catch {
170
+ }
171
+ try {
172
+ rmSync2(`${lockPath}.pid`, { force: true });
173
+ } catch {
174
+ }
175
+ }
176
+ }
177
+ var FileLockBusyError;
178
+ var init_fileLock = __esm({
179
+ "src/lib/fileLock.ts"() {
180
+ "use strict";
181
+ FileLockBusyError = class extends Error {
182
+ constructor(lockPath, holderPid, retryAfterSeconds, causeCode) {
183
+ const pidPart = holderPid != null ? `(PID=${holderPid})` : "(PID=unknown)";
184
+ super(`File lock at ${lockPath} held by another process ${pidPart} \u2014 try again in ${retryAfterSeconds}s`);
185
+ this.lockPath = lockPath;
186
+ this.holderPid = holderPid;
187
+ this.retryAfterSeconds = retryAfterSeconds;
188
+ this.causeCode = causeCode;
189
+ this.name = "FileLockBusyError";
190
+ }
191
+ lockPath;
192
+ holderPid;
193
+ retryAfterSeconds;
194
+ causeCode;
195
+ };
196
+ }
197
+ });
198
+
199
+ // src/lib/sqlite-loader.ts
200
+ import { createRequire } from "module";
201
+ import { spawnSync } from "child_process";
202
+ import { accessSync, appendFileSync, chmodSync as chmodSync2, constants as fsConstants, existsSync as existsSync2, mkdirSync as mkdirSync3 } from "fs";
203
+ import { dirname as dirname2, join } from "path";
204
+ function isNativeAbiError(err) {
205
+ const code = err?.code;
206
+ if (code === "ERR_DLOPEN_FAILED") return true;
207
+ const msg = err instanceof Error ? err.message : String(err);
208
+ 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);
209
+ }
210
+ function loadBetterSqlite3(opts = {}) {
211
+ if (_testCtor) return _testCtor;
212
+ if (opts.fresh) bustNativeCache();
213
+ return req("better-sqlite3");
214
+ }
215
+ function bustNativeCache() {
216
+ try {
217
+ const cache = req.cache;
218
+ if (!cache) return;
219
+ for (const key of Object.keys(cache)) {
220
+ if (/better[-_]sqlite3|better_sqlite3\.node|[\\/]bindings[\\/]/.test(key)) {
221
+ delete cache[key];
222
+ }
223
+ }
224
+ } catch {
225
+ }
226
+ }
227
+ function openDatabase(dbPath, opts = {}) {
228
+ const { selfHeal: selfHealOpt = true, ...ctorOpts } = opts;
229
+ const selfHeal = selfHealOpt && process.env.MASSU_HOOK_RUNTIME !== "1";
230
+ const Ctor = loadBetterSqlite3();
231
+ try {
232
+ return new Ctor(dbPath, ctorOpts);
233
+ } catch (err) {
234
+ if (!isNativeAbiError(err)) {
235
+ throw err;
236
+ }
237
+ if (!selfHeal) {
238
+ recordHealEvent({ phase: "skipped", reason: "self-heal-disabled", abiTo: process.versions.modules });
239
+ throw new MemoryEngineUnusableError("abi-mismatch", detailOf(err));
240
+ }
241
+ const result = (_testHeal ?? attemptNativeHeal)(err);
242
+ if (!result.healed && !result.contended) {
243
+ throw new MemoryEngineUnusableError(result.reason ?? "heal-failed", result.detail ?? detailOf(err));
244
+ }
245
+ try {
246
+ const FreshCtor = loadBetterSqlite3({ fresh: true });
247
+ return new FreshCtor(dbPath, ctorOpts);
248
+ } catch (retryErr) {
249
+ throw new MemoryEngineUnusableError("heal-failed", detailOf(retryErr));
250
+ }
251
+ }
252
+ }
253
+ function attemptNativeHeal(err) {
254
+ const start = Date.now();
255
+ const abiTo = process.versions.modules;
256
+ const abiFrom = parseAbiFrom(err);
257
+ let pkgDir;
258
+ try {
259
+ pkgDir = dirname2(req.resolve("better-sqlite3/package.json"));
260
+ } catch {
261
+ recordHealEvent({ phase: "skipped", reason: "not-resolvable", abiFrom, abiTo });
262
+ return { healed: false, reason: "missing", abiFrom, abiTo, detail: "better-sqlite3 not resolvable" };
263
+ }
264
+ if (!isWritable(pkgDir)) {
265
+ recordHealEvent({ phase: "skipped", reason: "dir-not-writable", abiFrom, abiTo });
266
+ return { healed: false, reason: "heal-failed", abiFrom, abiTo, detail: `install dir not writable: ${pkgDir}` };
267
+ }
268
+ recordHealEvent({ phase: "attempt", reason: "abi-mismatch", abiFrom, abiTo });
269
+ try {
270
+ return withFileLockSync(
271
+ join(credentialsDir(), "native-heal.lock"),
272
+ () => runRebuild(pkgDir, abiFrom, abiTo, start),
273
+ { blockMs: 6e4, staleMs: 3e5 }
274
+ );
275
+ } catch (lockErr) {
276
+ const detail = lockErr instanceof Error ? lockErr.message : String(lockErr);
277
+ recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, detail: `lock: ${detail}` });
278
+ return { healed: false, contended: true, reason: "heal-failed", abiFrom, abiTo, detail: `heal lock contended: ${detail}` };
279
+ }
280
+ }
281
+ function runRebuild(pkgDir, abiFrom, abiTo, start) {
282
+ try {
283
+ const FreshCtor = loadBetterSqlite3({ fresh: true });
284
+ new FreshCtor(":memory:").close();
285
+ const res2 = {
286
+ healed: true,
287
+ abiFrom,
288
+ abiTo,
289
+ durationMs: Date.now() - start,
290
+ detail: "already healed by a concurrent process"
291
+ };
292
+ recordHealEvent({ phase: "success", reason: "already-healed", abiFrom, abiTo, durationMs: res2.durationMs });
293
+ return res2;
294
+ } catch (probeErr) {
295
+ if (!isNativeAbiError(probeErr)) {
296
+ return { healed: false, reason: "heal-failed", abiFrom, abiTo, durationMs: Date.now() - start, detail: detailOf(probeErr) };
297
+ }
298
+ }
299
+ const prebuildBin = resolveLocalBin(pkgDir, "prebuild-install");
300
+ if (prebuildBin) {
301
+ const r = spawnSync(process.execPath, [prebuildBin], {
302
+ cwd: pkgDir,
303
+ encoding: "utf-8",
304
+ timeout: 12e4
305
+ });
306
+ if (r.status === 0) {
307
+ const res2 = { healed: true, method: "prebuild-install", abiFrom, abiTo, durationMs: Date.now() - start };
308
+ recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
309
+ return res2;
310
+ }
311
+ }
312
+ const gypBin = resolveNodeGypBin();
313
+ if (gypBin && hasCompiler()) {
314
+ const r = spawnSync(process.execPath, [gypBin, "rebuild", "--release"], {
315
+ cwd: pkgDir,
316
+ encoding: "utf-8",
317
+ timeout: 3e5
318
+ });
319
+ if (r.status === 0) {
320
+ const res2 = { healed: true, method: "node-gyp", abiFrom, abiTo, durationMs: Date.now() - start };
321
+ recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
322
+ return res2;
323
+ }
324
+ }
325
+ const res = {
326
+ healed: false,
327
+ reason: "heal-failed",
328
+ abiFrom,
329
+ abiTo,
330
+ durationMs: Date.now() - start,
331
+ detail: prebuildBin ? "prebuild-install failed; node-gyp fallback unavailable or failed" : "no prebuild-install bin resolvable"
332
+ };
333
+ recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, durationMs: res.durationMs });
334
+ return res;
335
+ }
336
+ function recordHealEvent(event) {
337
+ try {
338
+ const rec = {
339
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
340
+ node: process.versions.node,
341
+ platform: process.platform,
342
+ arch: process.arch,
343
+ ...event
344
+ };
345
+ const dir = credentialsDir();
346
+ mkdirSync3(dir, { recursive: true });
347
+ const file = join(dir, "native-heal-events.jsonl");
348
+ appendFileSync(file, JSON.stringify(rec) + "\n", "utf-8");
349
+ try {
350
+ chmodSync2(file, 384);
351
+ } catch {
352
+ }
353
+ } catch {
354
+ }
355
+ }
356
+ function parseAbiFrom(err) {
357
+ const msg = err instanceof Error ? err.message : String(err ?? "");
358
+ const m = /NODE_MODULE_VERSION (\d+)/.exec(msg);
359
+ if (m && /^\d+$/.test(m[1])) return m[1];
360
+ return void 0;
361
+ }
362
+ function detailOf(err) {
363
+ const msg = err instanceof Error ? err.message : String(err);
364
+ return msg.slice(0, 300);
365
+ }
366
+ function isWritable(dir) {
367
+ try {
368
+ accessSync(dir, fsConstants.W_OK);
369
+ return true;
370
+ } catch {
371
+ return false;
372
+ }
373
+ }
374
+ function resolveLocalBin(pkgDir, name) {
375
+ const candidates = [
376
+ join(pkgDir, "node_modules", ".bin", name),
377
+ // nested install
378
+ join(pkgDir, "..", ".bin", name),
379
+ // hoisted (node_modules/.bin)
380
+ join(pkgDir, "..", "..", "node_modules", ".bin", name)
381
+ // workspace hoist
382
+ ];
383
+ for (const c of candidates) {
384
+ if (existsSync2(c)) return c;
385
+ }
386
+ return void 0;
387
+ }
388
+ function resolveNodeGypBin() {
389
+ try {
390
+ return req.resolve("node-gyp/bin/node-gyp.js");
391
+ } catch {
392
+ return void 0;
393
+ }
394
+ }
395
+ function hasCompiler() {
396
+ for (const cc of ["cc", "clang", "gcc"]) {
397
+ try {
398
+ const r = spawnSync(cc, ["--version"], { encoding: "utf-8", timeout: 5e3 });
399
+ if (r.status === 0) return true;
400
+ } catch {
401
+ }
402
+ }
403
+ return false;
404
+ }
405
+ var req, NATIVE_DB_REMEDY, MemoryEngineUnusableError, _testCtor, _testHeal;
406
+ var init_sqlite_loader = __esm({
407
+ "src/lib/sqlite-loader.ts"() {
408
+ "use strict";
409
+ init_credentials();
410
+ init_fileLock();
411
+ req = createRequire(import.meta.url);
412
+ NATIVE_DB_REMEDY = "Run 'massu heal' to rebuild the database engine for your Node version, then restart your MCP client / Claude Code.";
413
+ MemoryEngineUnusableError = class extends Error {
414
+ reason;
415
+ remedy;
416
+ detail;
417
+ constructor(reason, detail) {
418
+ super(
419
+ `Massu memory engine is unusable (${reason}). ${NATIVE_DB_REMEDY}` + (detail ? ` [${detail}]` : "")
420
+ );
421
+ this.name = "MemoryEngineUnusableError";
422
+ this.reason = reason;
423
+ this.remedy = NATIVE_DB_REMEDY;
424
+ this.detail = detail;
425
+ }
426
+ };
427
+ _testCtor = null;
428
+ _testHeal = null;
429
+ }
430
+ });
431
+
432
+ // src/lib/memory-path.ts
433
+ function encodeMemoryDirName(projectRoot) {
434
+ return projectRoot.replace(/\//g, "-");
435
+ }
436
+ var init_memory_path = __esm({
437
+ "src/lib/memory-path.ts"() {
438
+ "use strict";
439
+ }
440
+ });
441
+
113
442
  // src/config-memory-schema.ts
114
443
  import { z } from "zod";
115
444
  var MemoryConfigSchema;
@@ -249,8 +578,8 @@ var init_config_memory_schema = __esm({
249
578
  });
250
579
 
251
580
  // src/config.ts
252
- import { resolve as resolve2, dirname } from "path";
253
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
581
+ import { resolve as resolve2, dirname as dirname3 } from "path";
582
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
254
583
  import { homedir as homedir2 } from "os";
255
584
  import { parse as parseYaml } from "yaml";
256
585
  import { z as z2 } from "zod";
@@ -258,22 +587,22 @@ function findProjectRoot() {
258
587
  const cwd = process.cwd();
259
588
  let dir = cwd;
260
589
  while (true) {
261
- if (existsSync2(resolve2(dir, "massu.config.yaml"))) {
590
+ if (existsSync3(resolve2(dir, "massu.config.yaml"))) {
262
591
  return dir;
263
592
  }
264
- const parent = dirname(dir);
593
+ const parent = dirname3(dir);
265
594
  if (parent === dir) break;
266
595
  dir = parent;
267
596
  }
268
597
  dir = cwd;
269
598
  while (true) {
270
- if (existsSync2(resolve2(dir, "package.json"))) {
599
+ if (existsSync3(resolve2(dir, "package.json"))) {
271
600
  return dir;
272
601
  }
273
- if (existsSync2(resolve2(dir, ".git"))) {
602
+ if (existsSync3(resolve2(dir, ".git"))) {
274
603
  return dir;
275
604
  }
276
- const parent = dirname(dir);
605
+ const parent = dirname3(dir);
277
606
  if (parent === dir) break;
278
607
  dir = parent;
279
608
  }
@@ -290,8 +619,8 @@ function getConfig() {
290
619
  const root = getProjectRoot();
291
620
  const configPath = resolve2(root, "massu.config.yaml");
292
621
  let rawYaml = {};
293
- if (existsSync2(configPath)) {
294
- const content = readFileSync2(configPath, "utf-8");
622
+ if (existsSync3(configPath)) {
623
+ const content = readFileSync3(configPath, "utf-8");
295
624
  rawYaml = parseYaml(content) ?? {};
296
625
  }
297
626
  const result = RawConfigSchema.safeParse(rawYaml);
@@ -372,9 +701,13 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
372
701
  const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
373
702
  if (resolvedKey.apiKey) {
374
703
  _config.cloud = {
375
- enabled: true,
376
704
  sync: { memory: true, analytics: true, audit: true },
377
705
  ..._config.cloud,
706
+ // Spread FIRST, then decide `enabled`, so a workspace that declares a `cloud:`
707
+ // block only to tune `requestTimeoutMs` does not disable its own sync. An
708
+ // EXPLICIT `enabled: false` is still honoured; `undefined` (not stated) means
709
+ // "a key resolved, so turn it on". (plan-2026-07-20-cloud-sync-timeout)
710
+ enabled: _config.cloud?.enabled ?? true,
378
711
  apiKey: resolvedKey.apiKey,
379
712
  endpoint: resolvedEndpoint
380
713
  };
@@ -594,9 +927,22 @@ var init_config = __esm({
594
927
  })).default([])
595
928
  }).optional();
596
929
  CloudConfigSchema = z2.object({
597
- enabled: z2.boolean().default(false),
930
+ // OPTIONAL, not `.default(false)`. A zod default is indistinguishable from an
931
+ // explicit value once parsed, so `.default(false)` meant that merely declaring a
932
+ // `cloud:` block (to set any OTHER key) silently produced `enabled: false`, which
933
+ // then overrode the `enabled: true` auto-enable below via object spread — turning
934
+ // cloud sync OFF as a side effect of tuning it. `undefined` now means "not stated",
935
+ // and the auto-enable preserves it. (plan-2026-07-20-cloud-sync-timeout)
936
+ enabled: z2.boolean().optional(),
598
937
  apiKey: z2.string().optional(),
599
938
  endpoint: z2.string().optional(),
939
+ // Per-request POST budget for the `/sync` ingest. cloud-sync.ts reads this
940
+ // (`(cloud as { requestTimeoutMs?: number }).requestTimeoutMs`) but it was ABSENT
941
+ // from this schema, and zod strips unknown keys — so the knob was unreachable from
942
+ // massu.config.yaml and the default could never be tuned. Measured 2026-07-20: a
943
+ // 423-observation payload takes ~9.2s against the live ingest. Capped at
944
+ // SYNC_DEADLINE_MS (20s) since the overall deadline clamps each attempt anyway.
945
+ requestTimeoutMs: z2.number().int().positive().max(2e4).optional(),
600
946
  sync: z2.object({
601
947
  memory: z2.boolean().default(true),
602
948
  analytics: z2.boolean().default(true),
@@ -833,27 +1179,26 @@ var init_memory_vector = __esm({
833
1179
  });
834
1180
 
835
1181
  // src/db-backup.ts
836
- import Database from "better-sqlite3";
837
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync2 } from "fs";
838
- import { resolve as resolve3, join, basename, dirname as dirname2 } from "path";
1182
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync3 } from "fs";
1183
+ import { resolve as resolve3, join as join2, basename, dirname as dirname4 } from "path";
839
1184
  import { homedir as homedir3 } from "os";
840
1185
  function dbBackupsRoot(home = homedir3()) {
841
1186
  return resolve3(home, ".massu", "db-backups");
842
1187
  }
843
1188
  function projectBackupDir(projectRoot, home = homedir3()) {
844
1189
  const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
845
- return join(dbBackupsRoot(home), slug);
1190
+ return join2(dbBackupsRoot(home), slug);
846
1191
  }
847
1192
  function backupStamp(nowMs) {
848
1193
  return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
849
1194
  }
850
1195
  function listDbBackups(projectRoot, home = homedir3()) {
851
1196
  const dir = projectBackupDir(projectRoot, home);
852
- if (!existsSync3(dir)) return [];
1197
+ if (!existsSync4(dir)) return [];
853
1198
  const out = [];
854
1199
  for (const f of readdirSync(dir)) {
855
1200
  if (!f.endsWith(".db")) continue;
856
- const p = join(dir, f);
1201
+ const p = join2(dir, f);
857
1202
  const st = statSync(p);
858
1203
  const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
859
1204
  out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
@@ -866,13 +1211,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
866
1211
  if (backups.length === 0) return false;
867
1212
  const newest = backups[0];
868
1213
  if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
869
- if (existsSync3(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
1214
+ if (existsSync4(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
870
1215
  return true;
871
1216
  }
872
1217
  function integrityOk(dbPath) {
873
1218
  let db = null;
874
1219
  try {
875
- db = new Database(dbPath, { readonly: true });
1220
+ db = openDatabase(dbPath, { readonly: true });
876
1221
  const rows = db.pragma("integrity_check");
877
1222
  return rows.length === 1 && rows[0].integrity_check === "ok";
878
1223
  } catch {
@@ -885,16 +1230,16 @@ function integrityOk(dbPath) {
885
1230
  }
886
1231
  }
887
1232
  function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
888
- if (!existsSync3(dbPath)) {
1233
+ if (!existsSync4(dbPath)) {
889
1234
  throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
890
1235
  }
891
1236
  const dir = projectBackupDir(projectRoot, home);
892
- mkdirSync2(dir, { recursive: true });
1237
+ mkdirSync4(dir, { recursive: true });
893
1238
  const name = basename(dbPath, ".db");
894
- const dest = join(dir, `${name}-${backupStamp(nowMs)}.db`);
1239
+ const dest = join2(dir, `${name}-${backupStamp(nowMs)}.db`);
895
1240
  let src = null;
896
1241
  try {
897
- src = new Database(dbPath, { readonly: true });
1242
+ src = openDatabase(dbPath, { readonly: true });
898
1243
  src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
899
1244
  } catch (err) {
900
1245
  throw new DbBackupError(
@@ -933,7 +1278,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
933
1278
  }
934
1279
  function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
935
1280
  try {
936
- if (!existsSync3(dbPath)) return null;
1281
+ if (!existsSync4(dbPath)) return null;
937
1282
  if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
938
1283
  return backupDb(projectRoot, dbPath, nowMs, home);
939
1284
  } catch (err) {
@@ -945,6 +1290,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
945
1290
  var init_db_backup = __esm({
946
1291
  "src/db-backup.ts"() {
947
1292
  "use strict";
1293
+ init_sqlite_loader();
948
1294
  DEFAULT_RETENTION = 5;
949
1295
  FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
950
1296
  DbBackupError = class extends Error {
@@ -1057,21 +1403,21 @@ var init_rule_candidate_store = __esm({
1057
1403
  });
1058
1404
 
1059
1405
  // src/hooks/lib/hook-failure-signal.ts
1060
- import { appendFileSync, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
1061
- import { join as join2, dirname as dirname3 } from "path";
1406
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync5 } from "fs";
1407
+ import { join as join3, dirname as dirname5 } from "path";
1062
1408
  function resolveFailureLogPath() {
1063
1409
  const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
1064
1410
  if (explicit) return explicit;
1065
1411
  let dir = process.cwd();
1066
1412
  for (let i = 0; i < 12; i++) {
1067
- if (existsSync4(join2(dir, ".massu")) || existsSync4(join2(dir, "massu.config.yaml"))) {
1068
- return join2(dir, ".massu", "hook-failures.jsonl");
1413
+ if (existsSync5(join3(dir, ".massu")) || existsSync5(join3(dir, "massu.config.yaml"))) {
1414
+ return join3(dir, ".massu", "hook-failures.jsonl");
1069
1415
  }
1070
- const parent = dirname3(dir);
1416
+ const parent = dirname5(dir);
1071
1417
  if (parent === dir) break;
1072
1418
  dir = parent;
1073
1419
  }
1074
- return join2(process.cwd(), ".massu", "hook-failures.jsonl");
1420
+ return join3(process.cwd(), ".massu", "hook-failures.jsonl");
1075
1421
  }
1076
1422
  function recordHookFailure(hook, error, context) {
1077
1423
  const record = {
@@ -1084,8 +1430,8 @@ function recordHookFailure(hook, error, context) {
1084
1430
  let wroteSomething = false;
1085
1431
  try {
1086
1432
  const path = resolveFailureLogPath();
1087
- mkdirSync3(dirname3(path), { recursive: true });
1088
- appendFileSync(path, JSON.stringify(record) + "\n", "utf-8");
1433
+ mkdirSync5(dirname5(path), { recursive: true });
1434
+ appendFileSync2(path, JSON.stringify(record) + "\n", "utf-8");
1089
1435
  wroteSomething = true;
1090
1436
  } catch {
1091
1437
  }
@@ -1128,9 +1474,9 @@ var init_hook_failure_signal = __esm({
1128
1474
  });
1129
1475
 
1130
1476
  // src/memory-embedder-tokenizer.ts
1131
- import { readFileSync as readFileSync3 } from "fs";
1477
+ import { readFileSync as readFileSync4 } from "fs";
1132
1478
  function loadVocab(vocabPath) {
1133
- const lines = readFileSync3(vocabPath, "utf-8").split("\n");
1479
+ const lines = readFileSync4(vocabPath, "utf-8").split("\n");
1134
1480
  const vocab = /* @__PURE__ */ new Map();
1135
1481
  for (let i = 0; i < lines.length; i++) {
1136
1482
  const tok = lines[i].replace(/\r$/, "");
@@ -1250,9 +1596,9 @@ var init_memory_embedder_tokenizer = __esm({
1250
1596
 
1251
1597
  // src/memory-embedder.ts
1252
1598
  import { fileURLToPath } from "url";
1253
- import { dirname as dirname4, join as join3 } from "path";
1254
- import { existsSync as existsSync5 } from "fs";
1255
- import { createRequire } from "module";
1599
+ import { dirname as dirname6, join as join4 } from "path";
1600
+ import { existsSync as existsSync6 } from "fs";
1601
+ import { createRequire as createRequire2 } from "module";
1256
1602
  function getActiveEmbedModel() {
1257
1603
  return _activeModel;
1258
1604
  }
@@ -1273,21 +1619,21 @@ function embeddingsDisabled() {
1273
1619
  }
1274
1620
  function resolveModelDir() {
1275
1621
  try {
1276
- let dir = dirname4(fileURLToPath(import.meta.url));
1622
+ let dir = dirname6(fileURLToPath(import.meta.url));
1277
1623
  let root = null;
1278
1624
  for (let i = 0; i < 8; i++) {
1279
- if (existsSync5(join3(dir, "package.json"))) {
1625
+ if (existsSync6(join4(dir, "package.json"))) {
1280
1626
  root = dir;
1281
1627
  break;
1282
1628
  }
1283
- const parent = dirname4(dir);
1629
+ const parent = dirname6(dir);
1284
1630
  if (parent === dir) break;
1285
1631
  dir = parent;
1286
1632
  }
1287
1633
  if (!root) return null;
1288
1634
  for (const rel of ["dist/embedder", "assets/embedder"]) {
1289
- const candidate = join3(root, rel);
1290
- if (existsSync5(join3(candidate, "model_quantized.onnx"))) return candidate;
1635
+ const candidate = join4(root, rel);
1636
+ if (existsSync6(join4(candidate, "model_quantized.onnx"))) return candidate;
1291
1637
  }
1292
1638
  return null;
1293
1639
  } catch {
@@ -1296,9 +1642,9 @@ function resolveModelDir() {
1296
1642
  }
1297
1643
  function resolveWasmDir() {
1298
1644
  try {
1299
- const req = createRequire(import.meta.url);
1300
- const main2 = req.resolve(ORT_PKG);
1301
- return dirname4(main2);
1645
+ const req2 = createRequire2(import.meta.url);
1646
+ const main2 = req2.resolve(ORT_PKG);
1647
+ return dirname6(main2);
1302
1648
  } catch {
1303
1649
  return null;
1304
1650
  }
@@ -1318,8 +1664,8 @@ async function getSession() {
1318
1664
  ort.env.wasm.proxy = false;
1319
1665
  ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
1320
1666
  _ortTensor = ort.Tensor;
1321
- const modelPath = join3(modelDir, "model_quantized.onnx");
1322
- const vocabPath = join3(modelDir, "vocab.txt");
1667
+ const modelPath = join4(modelDir, "model_quantized.onnx");
1668
+ const vocabPath = join4(modelDir, "vocab.txt");
1323
1669
  _vocab = loadVocab(vocabPath);
1324
1670
  const session = await ort.InferenceSession.create(modelPath, {
1325
1671
  executionProviders: ["wasm"],
@@ -1656,9 +2002,8 @@ __export(memory_db_exports, {
1656
2002
  upsertObservationEmbedding: () => upsertObservationEmbedding,
1657
2003
  usageWarmupElapsed: () => usageWarmupElapsed
1658
2004
  });
1659
- import Database2 from "better-sqlite3";
1660
- import { dirname as dirname5, basename as basename2 } from "path";
1661
- import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2005
+ import { dirname as dirname7, basename as basename2 } from "path";
2006
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
1662
2007
  function sanitizeFts5Query(raw) {
1663
2008
  const trimmed = raw.trim();
1664
2009
  if (!trimmed) return '""';
@@ -1674,12 +2019,12 @@ function sanitizeFts5QueryOr(raw) {
1674
2019
  }
1675
2020
  function getMemoryDb() {
1676
2021
  const dbPath = getResolvedPaths().memoryDbPath;
1677
- const dir = dirname5(dbPath);
1678
- if (!existsSync6(dir)) {
1679
- mkdirSync4(dir, { recursive: true });
2022
+ const dir = dirname7(dbPath);
2023
+ if (!existsSync7(dir)) {
2024
+ mkdirSync6(dir, { recursive: true });
1680
2025
  }
1681
- const preExisting = existsSync6(dbPath);
1682
- const db = new Database2(dbPath);
2026
+ const preExisting = existsSync7(dbPath);
2027
+ const db = openDatabase(dbPath);
1683
2028
  db.pragma("journal_mode = WAL");
1684
2029
  db.pragma("foreign_keys = ON");
1685
2030
  const onDisk = db.pragma("user_version", { simple: true });
@@ -3385,6 +3730,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
3385
3730
  var init_memory_db = __esm({
3386
3731
  "src/memory-db.ts"() {
3387
3732
  "use strict";
3733
+ init_sqlite_loader();
3388
3734
  init_config();
3389
3735
  init_memory_vector();
3390
3736
  init_db_backup();
@@ -3911,8 +4257,8 @@ init_config();
3911
4257
 
3912
4258
  // src/import-resolver.ts
3913
4259
  init_config();
3914
- import { readFileSync as readFileSync4, existsSync as existsSync7, statSync as statSync2 } from "fs";
3915
- import { resolve as resolve6, dirname as dirname6, join as join4 } from "path";
4260
+ import { readFileSync as readFileSync5, existsSync as existsSync8, statSync as statSync2 } from "fs";
4261
+ import { resolve as resolve6, dirname as dirname8, join as join5 } from "path";
3916
4262
 
3917
4263
  // src/security-utils.ts
3918
4264
  import { resolve as resolve5, normalize } from "path";
@@ -3973,21 +4319,21 @@ function resolveImportPath(specifier, fromFile) {
3973
4319
  const paths = getResolvedPaths();
3974
4320
  basePath = resolve6(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
3975
4321
  } else {
3976
- basePath = resolve6(dirname6(fromFile), specifier);
4322
+ basePath = resolve6(dirname8(fromFile), specifier);
3977
4323
  }
3978
- if (existsSync7(basePath) && !isDirectory(basePath)) {
4324
+ if (existsSync8(basePath) && !isDirectory(basePath)) {
3979
4325
  return toRelative(basePath);
3980
4326
  }
3981
4327
  const resolvedPaths = getResolvedPaths();
3982
4328
  for (const ext of resolvedPaths.extensions) {
3983
4329
  const withExt = basePath + ext;
3984
- if (existsSync7(withExt)) {
4330
+ if (existsSync8(withExt)) {
3985
4331
  return toRelative(withExt);
3986
4332
  }
3987
4333
  }
3988
4334
  for (const indexFile of resolvedPaths.indexFiles) {
3989
- const indexPath = join4(basePath, indexFile);
3990
- if (existsSync7(indexPath)) {
4335
+ const indexPath = join5(basePath, indexFile);
4336
+ if (existsSync8(indexPath)) {
3991
4337
  return toRelative(indexPath);
3992
4338
  }
3993
4339
  }
@@ -4009,7 +4355,7 @@ function toRelative(absPath) {
4009
4355
  }
4010
4356
 
4011
4357
  // src/validation-engine.ts
4012
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
4358
+ import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
4013
4359
  function getValidationChecks() {
4014
4360
  return getConfig().governance?.validation?.checks ?? {
4015
4361
  rule_compliance: true,
@@ -4037,7 +4383,7 @@ function validateFile(filePath, projectRoot) {
4037
4383
  });
4038
4384
  return checks;
4039
4385
  }
4040
- if (!existsSync8(absPath)) {
4386
+ if (!existsSync9(absPath)) {
4041
4387
  checks.push({
4042
4388
  name: "file_exists",
4043
4389
  severity: "error",
@@ -4046,7 +4392,7 @@ function validateFile(filePath, projectRoot) {
4046
4392
  });
4047
4393
  return checks;
4048
4394
  }
4049
- const source = readFileSync5(absPath, "utf-8");
4395
+ const source = readFileSync6(absPath, "utf-8");
4050
4396
  const lines = source.split("\n");
4051
4397
  if (activeChecks.rule_compliance !== false) {
4052
4398
  for (const ruleSet of config.rules) {
@@ -4145,7 +4491,7 @@ function storeValidationResult(db, filePath, checks, sessionId, validationType =
4145
4491
 
4146
4492
  // src/security-scorer.ts
4147
4493
  init_config();
4148
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
4494
+ import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
4149
4495
  var DEFAULT_SECURITY_PATTERNS = [
4150
4496
  {
4151
4497
  regex: /\bexec\s*\(\s*[`"'].*\$\{/,
@@ -4236,12 +4582,12 @@ function scoreFileSecurity(filePath, projectRoot) {
4236
4582
  }]
4237
4583
  };
4238
4584
  }
4239
- if (!existsSync9(absPath)) {
4585
+ if (!existsSync10(absPath)) {
4240
4586
  return { riskScore: 0, findings: [] };
4241
4587
  }
4242
4588
  let source;
4243
4589
  try {
4244
- source = readFileSync6(absPath, "utf-8");
4590
+ source = readFileSync7(absPath, "utf-8");
4245
4591
  } catch {
4246
4592
  return { riskScore: 0, findings: [] };
4247
4593
  }
@@ -4281,8 +4627,8 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
4281
4627
 
4282
4628
  // src/hooks/post-tool-use.ts
4283
4629
  init_config();
4284
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
4285
- import { join as join5, basename as pathBasename2, dirname as pathDirname, resolve as pathResolve } from "path";
4630
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync7, statSync as statSync3 } from "fs";
4631
+ import { join as join6, basename as pathBasename2, dirname as pathDirname, resolve as pathResolve } from "path";
4286
4632
 
4287
4633
  // src/lib/recurrence-incrementer.ts
4288
4634
  var ANSI = /\x1b\[[0-9;]*m/g;
@@ -4346,7 +4692,7 @@ function incrementRecurrenceCountsForScannerFailures(db, sessionId, scannerStdou
4346
4692
  init_hook_failure_signal();
4347
4693
 
4348
4694
  // src/memory-file-ingest.ts
4349
- import { readFileSync as readFileSync7, existsSync as existsSync10, readdirSync as readdirSync2 } from "fs";
4695
+ import { readFileSync as readFileSync8, existsSync as existsSync11, readdirSync as readdirSync2 } from "fs";
4350
4696
  init_memory_db();
4351
4697
  import { parse as parseYaml2 } from "yaml";
4352
4698
  import { basename as pathBasename } from "path";
@@ -4455,8 +4801,8 @@ function upsertMemoryFileMirror(db, f) {
4455
4801
  return "inserted";
4456
4802
  }
4457
4803
  function ingestMemoryFile(db, sessionId, filePath) {
4458
- if (!existsSync10(filePath)) return "skipped";
4459
- const content = readFileSync7(filePath, "utf-8");
4804
+ if (!existsSync11(filePath)) return "skipped";
4805
+ const content = readFileSync8(filePath, "utf-8");
4460
4806
  if (Buffer.byteLength(content, "utf-8") > MAX_MEMORY_FILE_BYTES) {
4461
4807
  process.stderr.write(
4462
4808
  `[massu] memory file exceeds ${MAX_MEMORY_FILE_BYTES} bytes, not ingested: ${filePath}
@@ -4730,12 +5076,12 @@ async function main() {
4730
5076
  } catch (err) {
4731
5077
  try {
4732
5078
  const projectRoot = process.cwd();
4733
- const dir = join5(projectRoot, ".massu", "rule-candidates");
4734
- if (!existsSync11(dir)) mkdirSync5(dir, { recursive: true });
4735
- const logPath = join5(dir, ".cr53-increment-failures.jsonl");
4736
- const pre = existsSync11(logPath) ? readFileSync8(logPath, "utf-8") : "";
5079
+ const dir = join6(projectRoot, ".massu", "rule-candidates");
5080
+ if (!existsSync12(dir)) mkdirSync7(dir, { recursive: true });
5081
+ const logPath = join6(dir, ".cr53-increment-failures.jsonl");
5082
+ const pre = existsSync12(logPath) ? readFileSync9(logPath, "utf-8") : "";
4737
5083
  const sep = pre && !pre.endsWith("\n") ? "\n" : "";
4738
- writeFileSync2(logPath, pre + sep + JSON.stringify({
5084
+ writeFileSync3(logPath, pre + sep + JSON.stringify({
4739
5085
  session_id,
4740
5086
  scanner_output_excerpt: (tool_response ?? "").slice(0, 200),
4741
5087
  error: err instanceof Error ? err.message : String(err),
@@ -4838,13 +5184,13 @@ var _conventionDefaults = {
4838
5184
  function readConventions(cwd) {
4839
5185
  try {
4840
5186
  const projectRoot = cwd ?? process.cwd();
4841
- const configPath = join5(projectRoot, "massu.config.yaml");
4842
- if (!existsSync11(configPath)) return _conventionDefaults;
5187
+ const configPath = join6(projectRoot, "massu.config.yaml");
5188
+ if (!existsSync12(configPath)) return _conventionDefaults;
4843
5189
  const mtimeMs = statSync3(configPath).mtimeMs;
4844
5190
  if (_cachedConventions !== null && _cachedConventionsPath === configPath && _cachedConventionsMtimeMs === mtimeMs) {
4845
5191
  return _cachedConventions;
4846
5192
  }
4847
- const content = readFileSync8(configPath, "utf-8");
5193
+ const content = readFileSync9(configPath, "utf-8");
4848
5194
  const parsed = parseYaml3(content);
4849
5195
  if (!parsed || typeof parsed !== "object") return _conventionDefaults;
4850
5196
  const conventions = parsed.conventions;
@@ -4876,11 +5222,11 @@ function isKnowledgeSourceFile(filePath) {
4876
5222
  function checkMemoryFileIntegrity(filePath) {
4877
5223
  const issues = [];
4878
5224
  try {
4879
- if (!existsSync11(filePath)) {
5225
+ if (!existsSync12(filePath)) {
4880
5226
  issues.push("MEMORY.md file does not exist after write");
4881
5227
  return issues;
4882
5228
  }
4883
- const content = readFileSync8(filePath, "utf-8");
5229
+ const content = readFileSync9(filePath, "utf-8");
4884
5230
  const lines = content.split("\n");
4885
5231
  const MAX_LINES = 200;
4886
5232
  if (lines.length > MAX_LINES) {