@massu/core 1.16.2 → 2.0.0

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.
@@ -49,16 +49,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
49
49
  ));
50
50
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
51
51
 
52
- // src/lib/memory-path.ts
53
- function encodeMemoryDirName(projectRoot) {
54
- return projectRoot.replace(/\//g, "-");
55
- }
56
- var init_memory_path = __esm({
57
- "src/lib/memory-path.ts"() {
58
- "use strict";
59
- }
60
- });
61
-
62
52
  // src/credentials.ts
63
53
  import { homedir } from "os";
64
54
  import { resolve } from "path";
@@ -127,6 +117,475 @@ var init_credentials = __esm({
127
117
  }
128
118
  });
129
119
 
120
+ // src/lib/fileLock.ts
121
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
122
+ import { dirname } from "path";
123
+ import * as lockfile from "proper-lockfile";
124
+ function readLockHolderPid(lockPath) {
125
+ try {
126
+ const raw = readFileSync2(`${lockPath}.pid`, "utf-8").trim();
127
+ const pid = Number.parseInt(raw, 10);
128
+ if (!Number.isFinite(pid) || pid <= 0) return null;
129
+ return pid;
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+ function busyWaitSync(ms) {
135
+ if (typeof SharedArrayBuffer === "undefined" || typeof Atomics === "undefined") {
136
+ throw new Error(
137
+ `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.`
138
+ );
139
+ }
140
+ const sab = new SharedArrayBuffer(4);
141
+ const view = new Int32Array(sab);
142
+ Atomics.wait(view, 0, 0, ms);
143
+ }
144
+ function withFileLockSync(lockPath, fn, opts = {}) {
145
+ mkdirSync2(dirname(lockPath), { recursive: true });
146
+ const staleMs = opts.staleMs ?? 3e4;
147
+ const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
148
+ const pollIntervalMs = opts.pollIntervalMs ?? 100;
149
+ const now = opts.now ?? Date.now;
150
+ const sleep = opts.sleep ?? busyWaitSync;
151
+ const makeBusyError = opts.errorFactory ?? ((path, pid, retrySeconds, code) => new FileLockBusyError(path, pid, retrySeconds, code));
152
+ let release = null;
153
+ const deadline = now() + blockMs;
154
+ for (; ; ) {
155
+ try {
156
+ release = lockfile.lockSync(lockPath, {
157
+ stale: staleMs,
158
+ retries: 0,
159
+ realpath: false
160
+ });
161
+ try {
162
+ writeFileSync2(`${lockPath}.pid`, String(process.pid), "utf-8");
163
+ } catch {
164
+ }
165
+ break;
166
+ } catch (err) {
167
+ const e = err;
168
+ const code = e.code;
169
+ if (code !== "ELOCKED" && code !== "EBUSY") {
170
+ throw err;
171
+ }
172
+ if (now() >= deadline) {
173
+ const holderPid = readLockHolderPid(lockPath);
174
+ const remainingMs = Math.max(0, deadline - now());
175
+ const retryAfterSeconds = blockMs === 0 ? Math.round(staleMs / 1e3) : Math.round(remainingMs / 1e3);
176
+ throw makeBusyError(lockPath, holderPid, retryAfterSeconds, code);
177
+ }
178
+ sleep(pollIntervalMs);
179
+ }
180
+ }
181
+ try {
182
+ return fn();
183
+ } finally {
184
+ try {
185
+ if (release) release();
186
+ } catch {
187
+ }
188
+ try {
189
+ rmSync2(`${lockPath}.pid`, { force: true });
190
+ } catch {
191
+ }
192
+ }
193
+ }
194
+ var FileLockBusyError;
195
+ var init_fileLock = __esm({
196
+ "src/lib/fileLock.ts"() {
197
+ "use strict";
198
+ FileLockBusyError = class extends Error {
199
+ constructor(lockPath, holderPid, retryAfterSeconds, causeCode) {
200
+ const pidPart = holderPid != null ? `(PID=${holderPid})` : "(PID=unknown)";
201
+ super(`File lock at ${lockPath} held by another process ${pidPart} \u2014 try again in ${retryAfterSeconds}s`);
202
+ this.lockPath = lockPath;
203
+ this.holderPid = holderPid;
204
+ this.retryAfterSeconds = retryAfterSeconds;
205
+ this.causeCode = causeCode;
206
+ this.name = "FileLockBusyError";
207
+ }
208
+ lockPath;
209
+ holderPid;
210
+ retryAfterSeconds;
211
+ causeCode;
212
+ };
213
+ }
214
+ });
215
+
216
+ // src/lib/sqlite-loader.ts
217
+ import { createRequire } from "module";
218
+ import { spawnSync } from "child_process";
219
+ import { accessSync, appendFileSync, chmodSync as chmodSync2, constants as fsConstants, existsSync as existsSync2, mkdirSync as mkdirSync3 } from "fs";
220
+ import { dirname as dirname2, join } from "path";
221
+ function isNativeAbiError(err) {
222
+ const code = err?.code;
223
+ if (code === "ERR_DLOPEN_FAILED") return true;
224
+ const msg = err instanceof Error ? err.message : String(err);
225
+ 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);
226
+ }
227
+ function loadBetterSqlite3(opts = {}) {
228
+ if (_testCtor) return _testCtor;
229
+ if (opts.fresh) bustNativeCache();
230
+ return req("better-sqlite3");
231
+ }
232
+ function bustNativeCache() {
233
+ try {
234
+ const cache = req.cache;
235
+ if (!cache) return;
236
+ for (const key of Object.keys(cache)) {
237
+ if (/better[-_]sqlite3|better_sqlite3\.node|[\\/]bindings[\\/]/.test(key)) {
238
+ delete cache[key];
239
+ }
240
+ }
241
+ } catch {
242
+ }
243
+ }
244
+ function openDatabase(dbPath, opts = {}) {
245
+ const { selfHeal: selfHealOpt = true, ...ctorOpts } = opts;
246
+ const selfHeal = selfHealOpt && process.env.MASSU_HOOK_RUNTIME !== "1";
247
+ const Ctor = loadBetterSqlite3();
248
+ try {
249
+ return new Ctor(dbPath, ctorOpts);
250
+ } catch (err) {
251
+ if (!isNativeAbiError(err)) {
252
+ throw err;
253
+ }
254
+ if (!selfHeal) {
255
+ recordHealEvent({ phase: "skipped", reason: "self-heal-disabled", abiTo: process.versions.modules });
256
+ throw new MemoryEngineUnusableError("abi-mismatch", detailOf(err));
257
+ }
258
+ const result = (_testHeal ?? attemptNativeHeal)(err);
259
+ if (!result.healed && !result.contended) {
260
+ throw new MemoryEngineUnusableError(result.reason ?? "heal-failed", result.detail ?? detailOf(err));
261
+ }
262
+ try {
263
+ const FreshCtor = loadBetterSqlite3({ fresh: true });
264
+ return new FreshCtor(dbPath, ctorOpts);
265
+ } catch (retryErr) {
266
+ throw new MemoryEngineUnusableError("heal-failed", detailOf(retryErr));
267
+ }
268
+ }
269
+ }
270
+ function attemptNativeHeal(err) {
271
+ const start = Date.now();
272
+ const abiTo = process.versions.modules;
273
+ const abiFrom = parseAbiFrom(err);
274
+ let pkgDir;
275
+ try {
276
+ pkgDir = dirname2(req.resolve("better-sqlite3/package.json"));
277
+ } catch {
278
+ recordHealEvent({ phase: "skipped", reason: "not-resolvable", abiFrom, abiTo });
279
+ return { healed: false, reason: "missing", abiFrom, abiTo, detail: "better-sqlite3 not resolvable" };
280
+ }
281
+ if (!isWritable(pkgDir)) {
282
+ recordHealEvent({ phase: "skipped", reason: "dir-not-writable", abiFrom, abiTo });
283
+ return { healed: false, reason: "heal-failed", abiFrom, abiTo, detail: `install dir not writable: ${pkgDir}` };
284
+ }
285
+ recordHealEvent({ phase: "attempt", reason: "abi-mismatch", abiFrom, abiTo });
286
+ try {
287
+ return withFileLockSync(
288
+ join(credentialsDir(), "native-heal.lock"),
289
+ () => runRebuild(pkgDir, abiFrom, abiTo, start),
290
+ { blockMs: 6e4, staleMs: 3e5 }
291
+ );
292
+ } catch (lockErr) {
293
+ const detail = lockErr instanceof Error ? lockErr.message : String(lockErr);
294
+ recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, detail: `lock: ${detail}` });
295
+ return { healed: false, contended: true, reason: "heal-failed", abiFrom, abiTo, detail: `heal lock contended: ${detail}` };
296
+ }
297
+ }
298
+ function runRebuild(pkgDir, abiFrom, abiTo, start) {
299
+ try {
300
+ const FreshCtor = loadBetterSqlite3({ fresh: true });
301
+ new FreshCtor(":memory:").close();
302
+ const res2 = {
303
+ healed: true,
304
+ abiFrom,
305
+ abiTo,
306
+ durationMs: Date.now() - start,
307
+ detail: "already healed by a concurrent process"
308
+ };
309
+ recordHealEvent({ phase: "success", reason: "already-healed", abiFrom, abiTo, durationMs: res2.durationMs });
310
+ return res2;
311
+ } catch (probeErr) {
312
+ if (!isNativeAbiError(probeErr)) {
313
+ return { healed: false, reason: "heal-failed", abiFrom, abiTo, durationMs: Date.now() - start, detail: detailOf(probeErr) };
314
+ }
315
+ }
316
+ const prebuildBin = resolveLocalBin(pkgDir, "prebuild-install");
317
+ if (prebuildBin) {
318
+ const r = spawnSync(process.execPath, [prebuildBin], {
319
+ cwd: pkgDir,
320
+ encoding: "utf-8",
321
+ timeout: 12e4
322
+ });
323
+ if (r.status === 0) {
324
+ const res2 = { healed: true, method: "prebuild-install", abiFrom, abiTo, durationMs: Date.now() - start };
325
+ recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
326
+ return res2;
327
+ }
328
+ }
329
+ const gypBin = resolveNodeGypBin();
330
+ if (gypBin && hasCompiler()) {
331
+ const r = spawnSync(process.execPath, [gypBin, "rebuild", "--release"], {
332
+ cwd: pkgDir,
333
+ encoding: "utf-8",
334
+ timeout: 3e5
335
+ });
336
+ if (r.status === 0) {
337
+ const res2 = { healed: true, method: "node-gyp", abiFrom, abiTo, durationMs: Date.now() - start };
338
+ recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
339
+ return res2;
340
+ }
341
+ }
342
+ const res = {
343
+ healed: false,
344
+ reason: "heal-failed",
345
+ abiFrom,
346
+ abiTo,
347
+ durationMs: Date.now() - start,
348
+ detail: prebuildBin ? "prebuild-install failed; node-gyp fallback unavailable or failed" : "no prebuild-install bin resolvable"
349
+ };
350
+ recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, durationMs: res.durationMs });
351
+ return res;
352
+ }
353
+ function recordHealEvent(event) {
354
+ try {
355
+ const rec = {
356
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
357
+ node: process.versions.node,
358
+ platform: process.platform,
359
+ arch: process.arch,
360
+ ...event
361
+ };
362
+ const dir = credentialsDir();
363
+ mkdirSync3(dir, { recursive: true });
364
+ const file = join(dir, "native-heal-events.jsonl");
365
+ appendFileSync(file, JSON.stringify(rec) + "\n", "utf-8");
366
+ try {
367
+ chmodSync2(file, 384);
368
+ } catch {
369
+ }
370
+ } catch {
371
+ }
372
+ }
373
+ function parseAbiFrom(err) {
374
+ const msg = err instanceof Error ? err.message : String(err ?? "");
375
+ const m = /NODE_MODULE_VERSION (\d+)/.exec(msg);
376
+ if (m && /^\d+$/.test(m[1])) return m[1];
377
+ return void 0;
378
+ }
379
+ function detailOf(err) {
380
+ const msg = err instanceof Error ? err.message : String(err);
381
+ return msg.slice(0, 300);
382
+ }
383
+ function isWritable(dir) {
384
+ try {
385
+ accessSync(dir, fsConstants.W_OK);
386
+ return true;
387
+ } catch {
388
+ return false;
389
+ }
390
+ }
391
+ function resolveLocalBin(pkgDir, name) {
392
+ const candidates = [
393
+ join(pkgDir, "node_modules", ".bin", name),
394
+ // nested install
395
+ join(pkgDir, "..", ".bin", name),
396
+ // hoisted (node_modules/.bin)
397
+ join(pkgDir, "..", "..", "node_modules", ".bin", name)
398
+ // workspace hoist
399
+ ];
400
+ for (const c of candidates) {
401
+ if (existsSync2(c)) return c;
402
+ }
403
+ return void 0;
404
+ }
405
+ function resolveNodeGypBin() {
406
+ try {
407
+ return req.resolve("node-gyp/bin/node-gyp.js");
408
+ } catch {
409
+ return void 0;
410
+ }
411
+ }
412
+ function hasCompiler() {
413
+ for (const cc of ["cc", "clang", "gcc"]) {
414
+ try {
415
+ const r = spawnSync(cc, ["--version"], { encoding: "utf-8", timeout: 5e3 });
416
+ if (r.status === 0) return true;
417
+ } catch {
418
+ }
419
+ }
420
+ return false;
421
+ }
422
+ var req, NATIVE_DB_REMEDY, MemoryEngineUnusableError, _testCtor, _testHeal;
423
+ var init_sqlite_loader = __esm({
424
+ "src/lib/sqlite-loader.ts"() {
425
+ "use strict";
426
+ init_credentials();
427
+ init_fileLock();
428
+ req = createRequire(import.meta.url);
429
+ NATIVE_DB_REMEDY = "Run 'massu heal' to rebuild the database engine for your Node version, then restart your MCP client / Claude Code.";
430
+ MemoryEngineUnusableError = class extends Error {
431
+ reason;
432
+ remedy;
433
+ detail;
434
+ constructor(reason, detail) {
435
+ super(
436
+ `Massu memory engine is unusable (${reason}). ${NATIVE_DB_REMEDY}` + (detail ? ` [${detail}]` : "")
437
+ );
438
+ this.name = "MemoryEngineUnusableError";
439
+ this.reason = reason;
440
+ this.remedy = NATIVE_DB_REMEDY;
441
+ this.detail = detail;
442
+ }
443
+ };
444
+ _testCtor = null;
445
+ _testHeal = null;
446
+ }
447
+ });
448
+
449
+ // src/db-driver.ts
450
+ import { createRequire as createRequire2 } from "module";
451
+ import { closeSync, existsSync as existsSync3, mkdtempSync, openSync, readFileSync as readFileSync3, rmSync as rmSync3, fsyncSync } from "fs";
452
+ import { tmpdir } from "os";
453
+ import { join as join2 } from "path";
454
+ function resolveDbEngine() {
455
+ return process.env[DB_ENGINE_ENV] === "better-sqlite3" ? "better-sqlite3" : DEFAULT_DB_ENGINE;
456
+ }
457
+ function nodeSqliteCtor() {
458
+ if (!_nodeCtor) _nodeCtor = req2("node:sqlite").DatabaseSync;
459
+ return _nodeCtor;
460
+ }
461
+ function plainRow(row) {
462
+ if (row == null || typeof row !== "object") return row;
463
+ return { ...row };
464
+ }
465
+ function openNodeSqlite(dbPath, opts) {
466
+ const Ctor = nodeSqliteCtor();
467
+ const raw = new Ctor(dbPath, {
468
+ open: true,
469
+ readOnly: !!opts.readonly,
470
+ // FAITHFULNESS: better-sqlite3's ACTUAL default is foreign_keys=ON (empirically
471
+ // verified — SQLite's own default is OFF, but bs3 enables it). massu was written +
472
+ // tested against that default, so the adapter must match it, or a store that relies
473
+ // on FK-ON-by-default (without an explicit pragma) would silently lose enforcement.
474
+ // Explicit `foreign_keys=OFF/ON` pragmas (e.g. memory-db bulk ops) still override.
475
+ enableForeignKeyConstraints: true,
476
+ allowExtension: false
477
+ });
478
+ let savepointSeq = 0;
479
+ const wrapStmt = (sql) => {
480
+ const st = raw.prepare(sql);
481
+ return {
482
+ run: (...p) => {
483
+ const r = st.run(...p);
484
+ return {
485
+ changes: typeof r.changes === "bigint" ? Number(r.changes) : r.changes,
486
+ lastInsertRowid: r.lastInsertRowid
487
+ };
488
+ },
489
+ get: (...p) => plainRow(st.get(...p)),
490
+ all: (...p) => st.all(...p).map(plainRow)
491
+ };
492
+ };
493
+ const pragma = (source, options) => {
494
+ const s = String(source).trim();
495
+ if (s.includes("=")) {
496
+ raw.exec(`PRAGMA ${s}`);
497
+ return void 0;
498
+ }
499
+ const rows = raw.prepare(`PRAGMA ${s}`).all().map(plainRow);
500
+ if (options?.simple) {
501
+ const first = rows[0];
502
+ return first ? Object.values(first)[0] : void 0;
503
+ }
504
+ return rows;
505
+ };
506
+ const transaction = (fn) => {
507
+ const run = (...args) => {
508
+ const nested = raw.isTransaction;
509
+ const name = `msp_${savepointSeq++}`;
510
+ if (nested) raw.exec(`SAVEPOINT ${name}`);
511
+ else raw.exec("BEGIN");
512
+ try {
513
+ const out = fn(...args);
514
+ if (nested) raw.exec(`RELEASE ${name}`);
515
+ else raw.exec("COMMIT");
516
+ return out;
517
+ } catch (e) {
518
+ if (nested) {
519
+ raw.exec(`ROLLBACK TO ${name}`);
520
+ raw.exec(`RELEASE ${name}`);
521
+ } else {
522
+ raw.exec("ROLLBACK");
523
+ }
524
+ throw e;
525
+ }
526
+ };
527
+ return run;
528
+ };
529
+ const serialize = () => {
530
+ if (raw.isTransaction) {
531
+ throw new Error(
532
+ "db-driver: .serialize() (node:sqlite engine) cannot run inside an open transaction \u2014 VACUUM is forbidden in a transaction. Serialize before BEGIN, or use MASSU_DB_ENGINE=better-sqlite3 for mid-transaction snapshots."
533
+ );
534
+ }
535
+ const dir = mkdtempSync(join2(tmpdir(), "massu-serialize-"));
536
+ const out = join2(dir, "snapshot.db");
537
+ try {
538
+ raw.exec(`VACUUM INTO '${out.replace(/'/g, "''")}'`);
539
+ const fd = openSync(out, "r");
540
+ try {
541
+ fsyncSync(fd);
542
+ } finally {
543
+ closeSync(fd);
544
+ }
545
+ return readFileSync3(out);
546
+ } finally {
547
+ rmSync3(dir, { recursive: true, force: true });
548
+ }
549
+ };
550
+ const handle = {
551
+ prepare: (sql) => wrapStmt(sql),
552
+ exec: (sql) => raw.exec(sql),
553
+ pragma,
554
+ transaction,
555
+ serialize,
556
+ close: () => raw.close()
557
+ };
558
+ return handle;
559
+ }
560
+ function openBetterSqlite3(dbPath, opts) {
561
+ return openDatabase(dbPath, opts);
562
+ }
563
+ function openDatabase2(dbPath, opts = {}) {
564
+ return resolveDbEngine() === "better-sqlite3" ? openBetterSqlite3(dbPath, opts) : openNodeSqlite(dbPath, opts);
565
+ }
566
+ var req2, DEFAULT_DB_ENGINE, DB_ENGINE_ENV, _nodeCtor;
567
+ var init_db_driver = __esm({
568
+ "src/db-driver.ts"() {
569
+ "use strict";
570
+ init_sqlite_loader();
571
+ init_sqlite_loader();
572
+ req2 = createRequire2(import.meta.url);
573
+ DEFAULT_DB_ENGINE = "node-sqlite";
574
+ DB_ENGINE_ENV = "MASSU_DB_ENGINE";
575
+ _nodeCtor = null;
576
+ }
577
+ });
578
+
579
+ // src/lib/memory-path.ts
580
+ function encodeMemoryDirName(projectRoot) {
581
+ return projectRoot.replace(/\//g, "-");
582
+ }
583
+ var init_memory_path = __esm({
584
+ "src/lib/memory-path.ts"() {
585
+ "use strict";
586
+ }
587
+ });
588
+
130
589
  // src/config-memory-schema.ts
131
590
  import { z } from "zod";
132
591
  var MemoryConfigSchema;
@@ -260,14 +719,40 @@ var init_config_memory_schema = __esm({
260
719
  // turn of EVERY session, so an unbounded index is a permanent context tax — and the
261
720
  // per-session cap bounds only the RATE, never the total.
262
721
  indexMaxLines: z.number().int().min(1).default(50)
722
+ }).default({}),
723
+ // --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
724
+ // A decision made in one of your repos can surface in another — opt-in per
725
+ // decision AND opt-in per repo, signed, verified, and materialized ONLY on
726
+ // explicit human acceptance. Local transport is FREE and zero-network.
727
+ //
728
+ // ⛔ BOTH switches default OFF, and OFF means NOTHING EXISTS — no registry, no
729
+ // keys, no inbox, no behavioural difference. Two INDEPENDENT opt-ins:
730
+ // • enabled — may this repo EXPORT its shareable decisions? (opt-in #1)
731
+ // • subscribe — which repo LABELS may this repo IMPORT from? (opt-in #2)
732
+ // `subscribe: []` means import NOTHING. There is deliberately NO `subscribe: all`
733
+ // — a repo you never named is a repo Massu never reads from.
734
+ share: z.object({
735
+ enabled: z.boolean().default(false),
736
+ subscribe: z.array(z.string()).default([]),
737
+ // C-04 — recall surfacing of cross-repo memories. `enabled` defaults true but is
738
+ // CONDITIONAL on `subscribe` being non-empty (empty by default), so the effective
739
+ // default is DORMANT: with `subscribe: []` the recall hook output is byte-identical
740
+ // to today's. `maxCrossRepoItems` caps how many accepted cross-repo items may appear
741
+ // per recall block (default 1); `minScore` is an OPTIONAL strictly-higher floor for
742
+ // cross-repo items (they are, by construction, less relevant than local ones).
743
+ recall: z.object({
744
+ enabled: z.boolean().default(true),
745
+ maxCrossRepoItems: z.number().int().min(0).default(1),
746
+ minScore: z.number().optional()
747
+ }).default({})
263
748
  }).default({})
264
749
  }).optional();
265
750
  }
266
751
  });
267
752
 
268
753
  // src/config.ts
269
- import { resolve as resolve2, dirname } from "path";
270
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
754
+ import { resolve as resolve2, dirname as dirname3 } from "path";
755
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
271
756
  import { homedir as homedir2 } from "os";
272
757
  import { parse as parseYaml } from "yaml";
273
758
  import { z as z2 } from "zod";
@@ -275,22 +760,22 @@ function findProjectRoot() {
275
760
  const cwd = process.cwd();
276
761
  let dir = cwd;
277
762
  while (true) {
278
- if (existsSync2(resolve2(dir, "massu.config.yaml"))) {
763
+ if (existsSync4(resolve2(dir, "massu.config.yaml"))) {
279
764
  return dir;
280
765
  }
281
- const parent = dirname(dir);
766
+ const parent = dirname3(dir);
282
767
  if (parent === dir) break;
283
768
  dir = parent;
284
769
  }
285
770
  dir = cwd;
286
771
  while (true) {
287
- if (existsSync2(resolve2(dir, "package.json"))) {
772
+ if (existsSync4(resolve2(dir, "package.json"))) {
288
773
  return dir;
289
774
  }
290
- if (existsSync2(resolve2(dir, ".git"))) {
775
+ if (existsSync4(resolve2(dir, ".git"))) {
291
776
  return dir;
292
777
  }
293
- const parent = dirname(dir);
778
+ const parent = dirname3(dir);
294
779
  if (parent === dir) break;
295
780
  dir = parent;
296
781
  }
@@ -307,8 +792,8 @@ function getConfig() {
307
792
  const root = getProjectRoot();
308
793
  const configPath = resolve2(root, "massu.config.yaml");
309
794
  let rawYaml = {};
310
- if (existsSync2(configPath)) {
311
- const content = readFileSync2(configPath, "utf-8");
795
+ if (existsSync4(configPath)) {
796
+ const content = readFileSync4(configPath, "utf-8");
312
797
  rawYaml = parseYaml(content) ?? {};
313
798
  }
314
799
  const result = RawConfigSchema.safeParse(rawYaml);
@@ -389,9 +874,13 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
389
874
  const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
390
875
  if (resolvedKey.apiKey) {
391
876
  _config.cloud = {
392
- enabled: true,
393
877
  sync: { memory: true, analytics: true, audit: true },
394
878
  ..._config.cloud,
879
+ // Spread FIRST, then decide `enabled`, so a workspace that declares a `cloud:`
880
+ // block only to tune `requestTimeoutMs` does not disable its own sync. An
881
+ // EXPLICIT `enabled: false` is still honoured; `undefined` (not stated) means
882
+ // "a key resolved, so turn it on". (plan-2026-07-20-cloud-sync-timeout)
883
+ enabled: _config.cloud?.enabled ?? true,
395
884
  apiKey: resolvedKey.apiKey,
396
885
  endpoint: resolvedEndpoint
397
886
  };
@@ -509,7 +998,6 @@ var init_config = __esm({
509
998
  custom_patterns: z2.array(CustomPatternSchema).default([])
510
999
  }).optional(),
511
1000
  adr: z2.object({
512
- detection_phrases: z2.array(z2.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
513
1001
  template: z2.string().default("default"),
514
1002
  storage: z2.string().default("database"),
515
1003
  output_dir: z2.string().default("docs/adr")
@@ -611,9 +1099,22 @@ var init_config = __esm({
611
1099
  })).default([])
612
1100
  }).optional();
613
1101
  CloudConfigSchema = z2.object({
614
- enabled: z2.boolean().default(false),
1102
+ // OPTIONAL, not `.default(false)`. A zod default is indistinguishable from an
1103
+ // explicit value once parsed, so `.default(false)` meant that merely declaring a
1104
+ // `cloud:` block (to set any OTHER key) silently produced `enabled: false`, which
1105
+ // then overrode the `enabled: true` auto-enable below via object spread — turning
1106
+ // cloud sync OFF as a side effect of tuning it. `undefined` now means "not stated",
1107
+ // and the auto-enable preserves it. (plan-2026-07-20-cloud-sync-timeout)
1108
+ enabled: z2.boolean().optional(),
615
1109
  apiKey: z2.string().optional(),
616
1110
  endpoint: z2.string().optional(),
1111
+ // Per-request POST budget for the `/sync` ingest. cloud-sync.ts reads this
1112
+ // (`(cloud as { requestTimeoutMs?: number }).requestTimeoutMs`) but it was ABSENT
1113
+ // from this schema, and zod strips unknown keys — so the knob was unreachable from
1114
+ // massu.config.yaml and the default could never be tuned. Measured 2026-07-20: a
1115
+ // 423-observation payload takes ~9.2s against the live ingest. Capped at
1116
+ // SYNC_DEADLINE_MS (20s) since the overall deadline clamps each attempt anyway.
1117
+ requestTimeoutMs: z2.number().int().positive().max(2e4).optional(),
617
1118
  sync: z2.object({
618
1119
  memory: z2.boolean().default(true),
619
1120
  analytics: z2.boolean().default(true),
@@ -850,27 +1351,26 @@ var init_memory_vector = __esm({
850
1351
  });
851
1352
 
852
1353
  // src/db-backup.ts
853
- import Database from "better-sqlite3";
854
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync2 } from "fs";
855
- import { resolve as resolve3, join, basename, dirname as dirname2 } from "path";
1354
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1355
+ import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
856
1356
  import { homedir as homedir3 } from "os";
857
1357
  function dbBackupsRoot(home = homedir3()) {
858
1358
  return resolve3(home, ".massu", "db-backups");
859
1359
  }
860
1360
  function projectBackupDir(projectRoot, home = homedir3()) {
861
1361
  const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
862
- return join(dbBackupsRoot(home), slug);
1362
+ return join3(dbBackupsRoot(home), slug);
863
1363
  }
864
1364
  function backupStamp(nowMs) {
865
1365
  return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
866
1366
  }
867
1367
  function listDbBackups(projectRoot, home = homedir3()) {
868
1368
  const dir = projectBackupDir(projectRoot, home);
869
- if (!existsSync3(dir)) return [];
1369
+ if (!existsSync5(dir)) return [];
870
1370
  const out = [];
871
1371
  for (const f of readdirSync(dir)) {
872
1372
  if (!f.endsWith(".db")) continue;
873
- const p = join(dir, f);
1373
+ const p = join3(dir, f);
874
1374
  const st = statSync(p);
875
1375
  const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
876
1376
  out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
@@ -883,13 +1383,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
883
1383
  if (backups.length === 0) return false;
884
1384
  const newest = backups[0];
885
1385
  if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
886
- if (existsSync3(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
1386
+ if (existsSync5(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
887
1387
  return true;
888
1388
  }
889
1389
  function integrityOk(dbPath) {
890
1390
  let db = null;
891
1391
  try {
892
- db = new Database(dbPath, { readonly: true });
1392
+ db = openDatabase2(dbPath, { readonly: true });
893
1393
  const rows = db.pragma("integrity_check");
894
1394
  return rows.length === 1 && rows[0].integrity_check === "ok";
895
1395
  } catch {
@@ -902,16 +1402,16 @@ function integrityOk(dbPath) {
902
1402
  }
903
1403
  }
904
1404
  function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
905
- if (!existsSync3(dbPath)) {
1405
+ if (!existsSync5(dbPath)) {
906
1406
  throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
907
1407
  }
908
1408
  const dir = projectBackupDir(projectRoot, home);
909
- mkdirSync2(dir, { recursive: true });
1409
+ mkdirSync4(dir, { recursive: true });
910
1410
  const name = basename(dbPath, ".db");
911
- const dest = join(dir, `${name}-${backupStamp(nowMs)}.db`);
1411
+ const dest = join3(dir, `${name}-${backupStamp(nowMs)}.db`);
912
1412
  let src = null;
913
1413
  try {
914
- src = new Database(dbPath, { readonly: true });
1414
+ src = openDatabase2(dbPath, { readonly: true });
915
1415
  src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
916
1416
  } catch (err) {
917
1417
  throw new DbBackupError(
@@ -950,7 +1450,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
950
1450
  }
951
1451
  function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
952
1452
  try {
953
- if (!existsSync3(dbPath)) return null;
1453
+ if (!existsSync5(dbPath)) return null;
954
1454
  if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
955
1455
  return backupDb(projectRoot, dbPath, nowMs, home);
956
1456
  } catch (err) {
@@ -962,6 +1462,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
962
1462
  var init_db_backup = __esm({
963
1463
  "src/db-backup.ts"() {
964
1464
  "use strict";
1465
+ init_db_driver();
965
1466
  DEFAULT_RETENTION = 5;
966
1467
  FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
967
1468
  DbBackupError = class extends Error {
@@ -1074,21 +1575,21 @@ var init_rule_candidate_store = __esm({
1074
1575
  });
1075
1576
 
1076
1577
  // src/hooks/lib/hook-failure-signal.ts
1077
- import { appendFileSync, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
1078
- import { join as join2, dirname as dirname3 } from "path";
1578
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
1579
+ import { join as join4, dirname as dirname5 } from "path";
1079
1580
  function resolveFailureLogPath() {
1080
1581
  const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
1081
1582
  if (explicit) return explicit;
1082
1583
  let dir = process.cwd();
1083
1584
  for (let i = 0; i < 12; i++) {
1084
- if (existsSync4(join2(dir, ".massu")) || existsSync4(join2(dir, "massu.config.yaml"))) {
1085
- return join2(dir, ".massu", "hook-failures.jsonl");
1585
+ if (existsSync6(join4(dir, ".massu")) || existsSync6(join4(dir, "massu.config.yaml"))) {
1586
+ return join4(dir, ".massu", "hook-failures.jsonl");
1086
1587
  }
1087
- const parent = dirname3(dir);
1588
+ const parent = dirname5(dir);
1088
1589
  if (parent === dir) break;
1089
1590
  dir = parent;
1090
1591
  }
1091
- return join2(process.cwd(), ".massu", "hook-failures.jsonl");
1592
+ return join4(process.cwd(), ".massu", "hook-failures.jsonl");
1092
1593
  }
1093
1594
  function recordHookFailure(hook, error, context) {
1094
1595
  const record = {
@@ -1101,8 +1602,8 @@ function recordHookFailure(hook, error, context) {
1101
1602
  let wroteSomething = false;
1102
1603
  try {
1103
1604
  const path = resolveFailureLogPath();
1104
- mkdirSync3(dirname3(path), { recursive: true });
1105
- appendFileSync(path, JSON.stringify(record) + "\n", "utf-8");
1605
+ mkdirSync5(dirname5(path), { recursive: true });
1606
+ appendFileSync2(path, JSON.stringify(record) + "\n", "utf-8");
1106
1607
  wroteSomething = true;
1107
1608
  } catch {
1108
1609
  }
@@ -1145,9 +1646,9 @@ var init_hook_failure_signal = __esm({
1145
1646
  });
1146
1647
 
1147
1648
  // src/memory-embedder-tokenizer.ts
1148
- import { readFileSync as readFileSync3 } from "fs";
1649
+ import { readFileSync as readFileSync5 } from "fs";
1149
1650
  function loadVocab(vocabPath) {
1150
- const lines = readFileSync3(vocabPath, "utf-8").split("\n");
1651
+ const lines = readFileSync5(vocabPath, "utf-8").split("\n");
1151
1652
  const vocab = /* @__PURE__ */ new Map();
1152
1653
  for (let i = 0; i < lines.length; i++) {
1153
1654
  const tok = lines[i].replace(/\r$/, "");
@@ -1267,9 +1768,9 @@ var init_memory_embedder_tokenizer = __esm({
1267
1768
 
1268
1769
  // src/memory-embedder.ts
1269
1770
  import { fileURLToPath } from "url";
1270
- import { dirname as dirname4, join as join3 } from "path";
1271
- import { existsSync as existsSync5 } from "fs";
1272
- import { createRequire } from "module";
1771
+ import { dirname as dirname6, join as join5 } from "path";
1772
+ import { existsSync as existsSync7 } from "fs";
1773
+ import { createRequire as createRequire3 } from "module";
1273
1774
  function getActiveEmbedModel() {
1274
1775
  return _activeModel;
1275
1776
  }
@@ -1290,21 +1791,21 @@ function embeddingsDisabled() {
1290
1791
  }
1291
1792
  function resolveModelDir() {
1292
1793
  try {
1293
- let dir = dirname4(fileURLToPath(import.meta.url));
1794
+ let dir = dirname6(fileURLToPath(import.meta.url));
1294
1795
  let root = null;
1295
1796
  for (let i = 0; i < 8; i++) {
1296
- if (existsSync5(join3(dir, "package.json"))) {
1797
+ if (existsSync7(join5(dir, "package.json"))) {
1297
1798
  root = dir;
1298
1799
  break;
1299
1800
  }
1300
- const parent = dirname4(dir);
1801
+ const parent = dirname6(dir);
1301
1802
  if (parent === dir) break;
1302
1803
  dir = parent;
1303
1804
  }
1304
1805
  if (!root) return null;
1305
1806
  for (const rel of ["dist/embedder", "assets/embedder"]) {
1306
- const candidate = join3(root, rel);
1307
- if (existsSync5(join3(candidate, "model_quantized.onnx"))) return candidate;
1807
+ const candidate = join5(root, rel);
1808
+ if (existsSync7(join5(candidate, "model_quantized.onnx"))) return candidate;
1308
1809
  }
1309
1810
  return null;
1310
1811
  } catch {
@@ -1313,9 +1814,9 @@ function resolveModelDir() {
1313
1814
  }
1314
1815
  function resolveWasmDir() {
1315
1816
  try {
1316
- const req = createRequire(import.meta.url);
1317
- const main2 = req.resolve(ORT_PKG);
1318
- return dirname4(main2);
1817
+ const req3 = createRequire3(import.meta.url);
1818
+ const main2 = req3.resolve(ORT_PKG);
1819
+ return dirname6(main2);
1319
1820
  } catch {
1320
1821
  return null;
1321
1822
  }
@@ -1335,8 +1836,8 @@ async function getSession() {
1335
1836
  ort.env.wasm.proxy = false;
1336
1837
  ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
1337
1838
  _ortTensor = ort.Tensor;
1338
- const modelPath = join3(modelDir, "model_quantized.onnx");
1339
- const vocabPath = join3(modelDir, "vocab.txt");
1839
+ const modelPath = join5(modelDir, "model_quantized.onnx");
1840
+ const vocabPath = join5(modelDir, "vocab.txt");
1340
1841
  _vocab = loadVocab(vocabPath);
1341
1842
  const session = await ort.InferenceSession.create(modelPath, {
1342
1843
  executionProviders: ["wasm"],
@@ -1657,6 +2158,7 @@ __export(memory_db_exports, {
1657
2158
  migrateAuditLogCheckExtension: () => migrateAuditLogCheckExtension,
1658
2159
  migrateMemoryFilesFor4B: () => migrateMemoryFilesFor4B,
1659
2160
  migrateObservationEmbeddingChunks: () => migrateObservationEmbeddingChunks,
2161
+ migrateSharedMemoryFor5B: () => migrateSharedMemoryFor5B,
1660
2162
  pruneOldConversationTurns: () => pruneOldConversationTurns,
1661
2163
  pruneOldObservations: () => pruneOldObservations,
1662
2164
  pruneToolCostEvents: () => pruneToolCostEvents,
@@ -1673,9 +2175,8 @@ __export(memory_db_exports, {
1673
2175
  upsertObservationEmbedding: () => upsertObservationEmbedding,
1674
2176
  usageWarmupElapsed: () => usageWarmupElapsed
1675
2177
  });
1676
- import Database2 from "better-sqlite3";
1677
- import { dirname as dirname5, basename as basename2 } from "path";
1678
- import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2178
+ import { dirname as dirname7, basename as basename2 } from "path";
2179
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
1679
2180
  function sanitizeFts5Query(raw) {
1680
2181
  const trimmed = raw.trim();
1681
2182
  if (!trimmed) return '""';
@@ -1691,12 +2192,12 @@ function sanitizeFts5QueryOr(raw) {
1691
2192
  }
1692
2193
  function getMemoryDb() {
1693
2194
  const dbPath = getResolvedPaths().memoryDbPath;
1694
- const dir = dirname5(dbPath);
1695
- if (!existsSync6(dir)) {
1696
- mkdirSync4(dir, { recursive: true });
2195
+ const dir = dirname7(dbPath);
2196
+ if (!existsSync8(dir)) {
2197
+ mkdirSync6(dir, { recursive: true });
1697
2198
  }
1698
- const preExisting = existsSync6(dbPath);
1699
- const db = new Database2(dbPath);
2199
+ const preExisting = existsSync8(dbPath);
2200
+ const db = openDatabase2(dbPath);
1700
2201
  db.pragma("journal_mode = WAL");
1701
2202
  db.pragma("foreign_keys = ON");
1702
2203
  const onDisk = db.pragma("user_version", { simple: true });
@@ -1772,6 +2273,39 @@ function migrateMemoryFilesFor4B(db) {
1772
2273
  db.exec(`ALTER TABLE ${add.table} ADD COLUMN ${add.name} ${add.decl}`);
1773
2274
  }
1774
2275
  }
2276
+ function migrateSharedMemoryFor5B(db) {
2277
+ const cols = db.prepare(`PRAGMA table_info(observations)`).all();
2278
+ if (cols.length > 0 && !cols.some((c) => c.name === "shareable")) {
2279
+ db.exec(`ALTER TABLE observations ADD COLUMN shareable INTEGER NOT NULL DEFAULT 0`);
2280
+ }
2281
+ db.exec(`
2282
+ CREATE TABLE IF NOT EXISTS shared_memory_pending (
2283
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2284
+ record_hash TEXT NOT NULL UNIQUE, -- identity + idempotency key (hex sha256)
2285
+ origin_repo_id TEXT NOT NULL, -- the signed origin repo_id (v4 UUID)
2286
+ origin_repo_label TEXT NOT NULL, -- re-slugged on read; NEVER trusted from the wire
2287
+ envelope_raw TEXT NOT NULL, -- the VERBATIM signed bytes; B-05 re-verifies these
2288
+ record_json TEXT NOT NULL, -- the single record's canonical JSON
2289
+ received_at_epoch INTEGER NOT NULL, -- epoch SECONDS (Slice-2 convention)
2290
+ accepted_at_epoch INTEGER, -- set on accept (B-05); NULL while pending
2291
+ refused_at_epoch INTEGER, -- set on refuse (B-06)
2292
+ expired_at_epoch INTEGER -- revocation \u21D2 EXPIRE, never DELETE (B-07 / S-3)
2293
+ );
2294
+ CREATE INDEX IF NOT EXISTS idx_smp_origin ON shared_memory_pending(origin_repo_id);
2295
+ CREATE INDEX IF NOT EXISTS idx_smp_received ON shared_memory_pending(received_at_epoch DESC);
2296
+ `);
2297
+ db.exec(`
2298
+ CREATE TABLE IF NOT EXISTS shared_memory_outbound (
2299
+ record_hash TEXT PRIMARY KEY, -- the exported record's canonical hash
2300
+ observation_id INTEGER NOT NULL, -- the source observations.id (for B-07 revocation)
2301
+ origin_repo_id TEXT NOT NULL, -- this repo's own repo_id at export time
2302
+ seq INTEGER NOT NULL, -- the envelope seq this record first went out in
2303
+ exported_at_epoch INTEGER NOT NULL, -- epoch SECONDS
2304
+ revoked_at_epoch INTEGER -- set when the source row is superseded/expired (B-07)
2305
+ );
2306
+ CREATE INDEX IF NOT EXISTS idx_smo_obs ON shared_memory_outbound(observation_id);
2307
+ `);
2308
+ }
1775
2309
  function migrateAuditLogCheckExtension(db) {
1776
2310
  const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
1777
2311
  if (!row) return;
@@ -1794,7 +2328,16 @@ function migrateAuditLogCheckExtension(db) {
1794
2328
  "memory_file_adopted_human",
1795
2329
  "memory_file_rendered",
1796
2330
  "memory_file_render_refused",
1797
- "memory_file_tombstoned"
2331
+ "memory_file_tombstoned",
2332
+ // B-10 (Slice 5) — the seven cross-repo shared-memory events. An existing DB whose
2333
+ // CHECK lacks any of these triggers the table rebuild below.
2334
+ "shared_memory_exported",
2335
+ "shared_memory_export_refused",
2336
+ "shared_memory_imported",
2337
+ "shared_memory_dropped",
2338
+ "shared_memory_accepted",
2339
+ "shared_memory_refused",
2340
+ "shared_memory_revoked"
1798
2341
  ];
1799
2342
  const checkClauseMatch = row.sql.match(/event_type\s+TEXT\s+NOT\s+NULL\s+CHECK\s*\(\s*event_type\s+IN\s*\(([\s\S]*?)\)\s*\)/i);
1800
2343
  if (checkClauseMatch) {
@@ -1813,7 +2356,12 @@ function migrateAuditLogCheckExtension(db) {
1813
2356
  'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
1814
2357
  'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
1815
2358
  'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
1816
- 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
2359
+ 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
2360
+ -- B-10 (Slice 5) \u2014 cross-repo shared-memory events (must mirror the CREATE +
2361
+ -- the expected[] list above, or the migration would loop rebuilding forever).
2362
+ 'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
2363
+ 'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
2364
+ 'shared_memory_revoked'
1817
2365
  )),
1818
2366
  actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
1819
2367
  model_id TEXT,
@@ -1890,6 +2438,12 @@ function initMemorySchema(db) {
1890
2438
  -- credential, or takes a snapshot. A Slice-5 synced memory arrives here with
1891
2439
  -- origin='team' and must never reach disk without CR-55's gates.
1892
2440
  origin TEXT NOT NULL DEFAULT 'local',
2441
+ -- B-01 (Slice 5, cross-repo surfacing): the per-decision SHARE opt-in. Set to 1
2442
+ -- ONLY by an explicit human act (massu memory share <id>); NEVER by a heuristic,
2443
+ -- score, LLM, hook, or consolidation pass. Export (B-02) reads WHERE shareable=1
2444
+ -- AND origin='local'. Defaults 0 so every pre-existing and machine-written row is
2445
+ -- un-shareable until a human says otherwise (fail-closed).
2446
+ shareable INTEGER NOT NULL DEFAULT 0,
1893
2447
  created_at TEXT NOT NULL,
1894
2448
  created_at_epoch INTEGER NOT NULL,
1895
2449
  FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
@@ -2163,7 +2717,13 @@ function initMemorySchema(db) {
2163
2717
  'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
2164
2718
  'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
2165
2719
  'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
2166
- 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
2720
+ 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
2721
+ -- B-10 (Slice 5): cross-repo shared-memory observability. Without these seven,
2722
+ -- nobody could ever answer "what has crossed between my repos?" (and an ingest
2723
+ -- catch would swallow the CHECK violation, silently producing NOTHING).
2724
+ 'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
2725
+ 'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
2726
+ 'shared_memory_revoked'
2167
2727
  )),
2168
2728
  actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
2169
2729
  model_id TEXT,
@@ -2532,6 +3092,7 @@ function initMemorySchema(db) {
2532
3092
  `);
2533
3093
  migrateObservationEmbeddingChunks(db);
2534
3094
  migrateMemoryFilesFor4B(db);
3095
+ migrateSharedMemoryFor5B(db);
2535
3096
  ensureRuleCandidatesTable(db);
2536
3097
  migrateRuleDelivery(db);
2537
3098
  }
@@ -3396,54 +3957,133 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
3396
3957
  };
3397
3958
  }
3398
3959
  }
3399
- return bestMatch;
3960
+ return bestMatch;
3961
+ }
3962
+ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
3963
+ var init_memory_db = __esm({
3964
+ "src/memory-db.ts"() {
3965
+ "use strict";
3966
+ init_db_driver();
3967
+ init_config();
3968
+ init_memory_vector();
3969
+ init_db_backup();
3970
+ init_rule_delivery();
3971
+ init_rule_candidate_store();
3972
+ init_hook_failure_signal();
3973
+ init_memory_embed_sweep();
3974
+ MEMORY_SCHEMA_VERSION = 1;
3975
+ TOOL_COST_EVENTS_RETENTION_DAYS = 90;
3976
+ MAX_DRAFT_TEXT_LEN = 16384;
3977
+ FUNNEL_EVENT_OUTBOX_CAP = 2e4;
3978
+ _temporalColCache = /* @__PURE__ */ new WeakMap();
3979
+ CONSOLIDATION_LESSON_EVIDENCE = "consolidation:session-summary";
3980
+ MEMORY_FILE_TITLE_PREFIX = "[memory-file] ";
3981
+ MEMORY_FILE_TITLE_LIKE = `${MEMORY_FILE_TITLE_PREFIX}%`;
3982
+ USAGE_COUNTER_ARMED_KEY = "usage_counter_armed_epoch";
3983
+ }
3984
+ });
3985
+
3986
+ // src/memory-authorship.ts
3987
+ import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3988
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync7, chmodSync as chmodSync3, statSync as statSync2 } from "fs";
3989
+ import { homedir as homedir4 } from "os";
3990
+ import { resolve as resolve5 } from "path";
3991
+ function renderKeyPath(home = homedir4()) {
3992
+ return resolve5(home, ".massu", "render-key");
3993
+ }
3994
+ function readRenderKey(home = homedir4()) {
3995
+ const p = renderKeyPath(home);
3996
+ try {
3997
+ if (!existsSync9(p)) return void 0;
3998
+ const raw = readFileSync6(p);
3999
+ if (raw.length !== 32) return void 0;
4000
+ return raw;
4001
+ } catch {
4002
+ return void 0;
4003
+ }
4004
+ }
4005
+ function ensureRenderKey(home = homedir4()) {
4006
+ const existing = readRenderKey(home);
4007
+ if (existing) return existing;
4008
+ const p = renderKeyPath(home);
4009
+ try {
4010
+ const dir = resolve5(home, ".massu");
4011
+ mkdirSync7(dir, { recursive: true, mode: 448 });
4012
+ try {
4013
+ chmodSync3(dir, 448);
4014
+ } catch {
4015
+ }
4016
+ const key = randomBytes(32);
4017
+ writeFileSync3(p, key, { mode: 384 });
4018
+ try {
4019
+ chmodSync3(p, 384);
4020
+ } catch {
4021
+ }
4022
+ return key;
4023
+ } catch {
4024
+ return void 0;
4025
+ }
4026
+ }
4027
+ function mintAuthorship(body, home = homedir4()) {
4028
+ try {
4029
+ const key = ensureRenderKey(home);
4030
+ if (!key) return null;
4031
+ return createHmac("sha256", key).update(body, "utf8").digest("hex");
4032
+ } catch {
4033
+ return null;
4034
+ }
4035
+ }
4036
+ function verifyAuthorship(body, frontmatter, storeRow, home = homedir4()) {
4037
+ try {
4038
+ if (storeRow?.adopted_human_at_epoch != null) return false;
4039
+ const key = readRenderKey(home);
4040
+ if (!key) return false;
4041
+ const claimed = frontmatter?.[RENDER_MAC_KEY];
4042
+ if (typeof claimed !== "string" || claimed.length === 0) return false;
4043
+ const expected = createHmac("sha256", key).update(body, "utf8").digest("hex");
4044
+ const a = Buffer.from(claimed, "utf8");
4045
+ const b = Buffer.from(expected, "utf8");
4046
+ if (a.length !== b.length) return false;
4047
+ return timingSafeEqual(a, b);
4048
+ } catch {
4049
+ return false;
4050
+ }
3400
4051
  }
3401
- var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
3402
- var init_memory_db = __esm({
3403
- "src/memory-db.ts"() {
4052
+ function extractRenderMac(frontmatter) {
4053
+ const v = frontmatter?.[RENDER_MAC_KEY];
4054
+ return typeof v === "string" && v.length > 0 ? v : null;
4055
+ }
4056
+ var RENDER_MAC_KEY;
4057
+ var init_memory_authorship = __esm({
4058
+ "src/memory-authorship.ts"() {
3404
4059
  "use strict";
3405
- init_config();
3406
- init_memory_vector();
3407
- init_db_backup();
3408
- init_rule_delivery();
3409
- init_rule_candidate_store();
3410
- init_hook_failure_signal();
3411
- init_memory_embed_sweep();
3412
- MEMORY_SCHEMA_VERSION = 1;
3413
- TOOL_COST_EVENTS_RETENTION_DAYS = 90;
3414
- MAX_DRAFT_TEXT_LEN = 16384;
3415
- FUNNEL_EVENT_OUTBOX_CAP = 2e4;
3416
- _temporalColCache = /* @__PURE__ */ new WeakMap();
3417
- CONSOLIDATION_LESSON_EVIDENCE = "consolidation:session-summary";
3418
- MEMORY_FILE_TITLE_PREFIX = "[memory-file] ";
3419
- MEMORY_FILE_TITLE_LIKE = `${MEMORY_FILE_TITLE_PREFIX}%`;
3420
- USAGE_COUNTER_ARMED_KEY = "usage_counter_armed_epoch";
4060
+ RENDER_MAC_KEY = "massu_render_mac";
3421
4061
  }
3422
4062
  });
3423
4063
 
3424
4064
  // src/lib/safe-write.ts
3425
4065
  import {
3426
- writeFileSync as writeFileSync2,
4066
+ writeFileSync as writeFileSync4,
3427
4067
  renameSync,
3428
- openSync,
3429
- fsyncSync,
3430
- closeSync,
3431
- existsSync as existsSync7,
4068
+ openSync as openSync2,
4069
+ fsyncSync as fsyncSync2,
4070
+ closeSync as closeSync2,
4071
+ existsSync as existsSync10,
3432
4072
  realpathSync,
3433
4073
  unlinkSync as unlinkSync2,
3434
- accessSync,
3435
- statSync as statSync2,
3436
- chmodSync as chmodSync2,
4074
+ accessSync as accessSync2,
4075
+ statSync as statSync3,
4076
+ chmodSync as chmodSync4,
3437
4077
  constants
3438
4078
  } from "fs";
3439
- import { dirname as dirname6, resolve as resolve5, relative, isAbsolute, basename as basename3, sep } from "path";
4079
+ import { dirname as dirname8, resolve as resolve6, relative, isAbsolute, basename as basename3, sep } from "path";
3440
4080
  import { createHash as createHash2 } from "crypto";
3441
4081
  function assertContainedIn(rootDir, candidate, opts = {}) {
3442
4082
  if (candidate.includes("\0")) {
3443
4083
  throw new PathEscapeError("path contains a NUL byte");
3444
4084
  }
3445
- const root = resolve5(rootDir);
3446
- const abs = resolve5(root, candidate);
4085
+ const root = resolve6(rootDir);
4086
+ const abs = resolve6(root, candidate);
3447
4087
  if (!opts.allowNested) {
3448
4088
  const rel = relative(root, abs);
3449
4089
  if (rel.includes(sep)) {
@@ -3462,14 +4102,14 @@ function assertContainedIn(rootDir, candidate, opts = {}) {
3462
4102
  }
3463
4103
  function isContainedIn(rootDir, candidate, opts = {}) {
3464
4104
  if (candidate.includes("\0")) return false;
3465
- const root = resolve5(rootDir);
3466
- const abs = resolve5(root, candidate);
4105
+ const root = resolve6(rootDir);
4106
+ const abs = resolve6(root, candidate);
3467
4107
  const rel = relative(root, abs);
3468
4108
  if (rel === "" && !opts.allowRoot) return false;
3469
4109
  if (rel.startsWith("..") || isAbsolute(rel)) return false;
3470
4110
  let probe = abs;
3471
- while (!existsSync7(probe) && probe.length > 1) {
3472
- const parent = dirname6(probe);
4111
+ while (!existsSync10(probe) && probe.length > 1) {
4112
+ const parent = dirname8(probe);
3473
4113
  if (parent === probe) break;
3474
4114
  probe = parent;
3475
4115
  }
@@ -3486,31 +4126,31 @@ function isContainedIn(rootDir, candidate, opts = {}) {
3486
4126
  return !realRel.startsWith("..") && !isAbsolute(realRel);
3487
4127
  }
3488
4128
  function atomicWriteFileSync(destPath, contents) {
3489
- const dir = dirname6(destPath);
3490
- const tmp = resolve5(dir, `.${basename3(destPath)}.massu-tmp-${process.pid}`);
4129
+ const dir = dirname8(destPath);
4130
+ const tmp = resolve6(dir, `.${basename3(destPath)}.massu-tmp-${process.pid}`);
3491
4131
  let destMode;
3492
- if (existsSync7(destPath)) {
3493
- accessSync(destPath, constants.W_OK);
3494
- destMode = statSync2(destPath).mode & 511;
4132
+ if (existsSync10(destPath)) {
4133
+ accessSync2(destPath, constants.W_OK);
4134
+ destMode = statSync3(destPath).mode & 511;
3495
4135
  }
3496
4136
  let fd;
3497
4137
  try {
3498
- writeFileSync2(tmp, contents, "utf-8");
3499
- if (destMode !== void 0) chmodSync2(tmp, destMode);
3500
- fd = openSync(tmp, "r+");
3501
- fsyncSync(fd);
3502
- closeSync(fd);
4138
+ writeFileSync4(tmp, contents, "utf-8");
4139
+ if (destMode !== void 0) chmodSync4(tmp, destMode);
4140
+ fd = openSync2(tmp, "r+");
4141
+ fsyncSync2(fd);
4142
+ closeSync2(fd);
3503
4143
  fd = void 0;
3504
4144
  renameSync(tmp, destPath);
3505
4145
  } catch (err) {
3506
4146
  if (fd !== void 0) {
3507
4147
  try {
3508
- closeSync(fd);
4148
+ closeSync2(fd);
3509
4149
  } catch {
3510
4150
  }
3511
4151
  }
3512
4152
  try {
3513
- if (existsSync7(tmp)) unlinkSync2(tmp);
4153
+ if (existsSync10(tmp)) unlinkSync2(tmp);
3514
4154
  } catch {
3515
4155
  }
3516
4156
  throw err;
@@ -3587,183 +4227,9 @@ var init_safe_write = __esm({
3587
4227
  }
3588
4228
  });
3589
4229
 
3590
- // src/memory-authorship.ts
3591
- import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3592
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync5, chmodSync as chmodSync3, statSync as statSync3 } from "fs";
3593
- import { homedir as homedir4 } from "os";
3594
- import { resolve as resolve6 } from "path";
3595
- function renderKeyPath(home = homedir4()) {
3596
- return resolve6(home, ".massu", "render-key");
3597
- }
3598
- function readRenderKey(home = homedir4()) {
3599
- const p = renderKeyPath(home);
3600
- try {
3601
- if (!existsSync8(p)) return void 0;
3602
- const raw = readFileSync4(p);
3603
- if (raw.length !== 32) return void 0;
3604
- return raw;
3605
- } catch {
3606
- return void 0;
3607
- }
3608
- }
3609
- function ensureRenderKey(home = homedir4()) {
3610
- const existing = readRenderKey(home);
3611
- if (existing) return existing;
3612
- const p = renderKeyPath(home);
3613
- try {
3614
- const dir = resolve6(home, ".massu");
3615
- mkdirSync5(dir, { recursive: true, mode: 448 });
3616
- try {
3617
- chmodSync3(dir, 448);
3618
- } catch {
3619
- }
3620
- const key = randomBytes(32);
3621
- writeFileSync3(p, key, { mode: 384 });
3622
- try {
3623
- chmodSync3(p, 384);
3624
- } catch {
3625
- }
3626
- return key;
3627
- } catch {
3628
- return void 0;
3629
- }
3630
- }
3631
- function mintAuthorship(body, home = homedir4()) {
3632
- try {
3633
- const key = ensureRenderKey(home);
3634
- if (!key) return null;
3635
- return createHmac("sha256", key).update(body, "utf8").digest("hex");
3636
- } catch {
3637
- return null;
3638
- }
3639
- }
3640
- function verifyAuthorship(body, frontmatter, storeRow, home = homedir4()) {
3641
- try {
3642
- if (storeRow?.adopted_human_at_epoch != null) return false;
3643
- const key = readRenderKey(home);
3644
- if (!key) return false;
3645
- const claimed = frontmatter?.[RENDER_MAC_KEY];
3646
- if (typeof claimed !== "string" || claimed.length === 0) return false;
3647
- const expected = createHmac("sha256", key).update(body, "utf8").digest("hex");
3648
- const a = Buffer.from(claimed, "utf8");
3649
- const b = Buffer.from(expected, "utf8");
3650
- if (a.length !== b.length) return false;
3651
- return timingSafeEqual(a, b);
3652
- } catch {
3653
- return false;
3654
- }
3655
- }
3656
- function extractRenderMac(frontmatter) {
3657
- const v = frontmatter?.[RENDER_MAC_KEY];
3658
- return typeof v === "string" && v.length > 0 ? v : null;
3659
- }
3660
- var RENDER_MAC_KEY;
3661
- var init_memory_authorship = __esm({
3662
- "src/memory-authorship.ts"() {
3663
- "use strict";
3664
- RENDER_MAC_KEY = "massu_render_mac";
3665
- }
3666
- });
3667
-
3668
- // src/lib/fileLock.ts
3669
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
3670
- import { dirname as dirname7 } from "path";
3671
- import * as lockfile from "proper-lockfile";
3672
- function readLockHolderPid(lockPath) {
3673
- try {
3674
- const raw = readFileSync5(`${lockPath}.pid`, "utf-8").trim();
3675
- const pid = Number.parseInt(raw, 10);
3676
- if (!Number.isFinite(pid) || pid <= 0) return null;
3677
- return pid;
3678
- } catch {
3679
- return null;
3680
- }
3681
- }
3682
- function busyWaitSync(ms) {
3683
- if (typeof SharedArrayBuffer === "undefined" || typeof Atomics === "undefined") {
3684
- throw new Error(
3685
- `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.`
3686
- );
3687
- }
3688
- const sab = new SharedArrayBuffer(4);
3689
- const view = new Int32Array(sab);
3690
- Atomics.wait(view, 0, 0, ms);
3691
- }
3692
- function withFileLockSync(lockPath, fn, opts = {}) {
3693
- mkdirSync6(dirname7(lockPath), { recursive: true });
3694
- const staleMs = opts.staleMs ?? 3e4;
3695
- const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
3696
- const pollIntervalMs = opts.pollIntervalMs ?? 100;
3697
- const now = opts.now ?? Date.now;
3698
- const sleep = opts.sleep ?? busyWaitSync;
3699
- const makeBusyError = opts.errorFactory ?? ((path, pid, retrySeconds, code) => new FileLockBusyError(path, pid, retrySeconds, code));
3700
- let release = null;
3701
- const deadline = now() + blockMs;
3702
- for (; ; ) {
3703
- try {
3704
- release = lockfile.lockSync(lockPath, {
3705
- stale: staleMs,
3706
- retries: 0,
3707
- realpath: false
3708
- });
3709
- try {
3710
- writeFileSync4(`${lockPath}.pid`, String(process.pid), "utf-8");
3711
- } catch {
3712
- }
3713
- break;
3714
- } catch (err) {
3715
- const e = err;
3716
- const code = e.code;
3717
- if (code !== "ELOCKED" && code !== "EBUSY") {
3718
- throw err;
3719
- }
3720
- if (now() >= deadline) {
3721
- const holderPid = readLockHolderPid(lockPath);
3722
- const remainingMs = Math.max(0, deadline - now());
3723
- const retryAfterSeconds = blockMs === 0 ? Math.round(staleMs / 1e3) : Math.round(remainingMs / 1e3);
3724
- throw makeBusyError(lockPath, holderPid, retryAfterSeconds, code);
3725
- }
3726
- sleep(pollIntervalMs);
3727
- }
3728
- }
3729
- try {
3730
- return fn();
3731
- } finally {
3732
- try {
3733
- if (release) release();
3734
- } catch {
3735
- }
3736
- try {
3737
- rmSync3(`${lockPath}.pid`, { force: true });
3738
- } catch {
3739
- }
3740
- }
3741
- }
3742
- var FileLockBusyError;
3743
- var init_fileLock = __esm({
3744
- "src/lib/fileLock.ts"() {
3745
- "use strict";
3746
- FileLockBusyError = class extends Error {
3747
- constructor(lockPath, holderPid, retryAfterSeconds, causeCode) {
3748
- const pidPart = holderPid != null ? `(PID=${holderPid})` : "(PID=unknown)";
3749
- super(`File lock at ${lockPath} held by another process ${pidPart} \u2014 try again in ${retryAfterSeconds}s`);
3750
- this.lockPath = lockPath;
3751
- this.holderPid = holderPid;
3752
- this.retryAfterSeconds = retryAfterSeconds;
3753
- this.causeCode = causeCode;
3754
- this.name = "FileLockBusyError";
3755
- }
3756
- lockPath;
3757
- holderPid;
3758
- retryAfterSeconds;
3759
- causeCode;
3760
- };
3761
- }
3762
- });
3763
-
3764
4230
  // src/memory-index-region.ts
3765
- import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
3766
- import { dirname as dirname8, join as join4 } from "path";
4231
+ import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
4232
+ import { dirname as dirname9, join as join6 } from "path";
3767
4233
  function parseRegion(content) {
3768
4234
  const begins = countOccurrences(content, BEGIN_SENTINEL);
3769
4235
  const ends = countOccurrences(content, END_SENTINEL);
@@ -3849,7 +4315,7 @@ function assertOutsideRegionUnchanged(pre, post) {
3849
4315
  }
3850
4316
  }
3851
4317
  function memoryIndexLockPath(memoryDir) {
3852
- return join4(dirname8(memoryDir), ".massu-memory-index.lock");
4318
+ return join6(dirname9(memoryDir), ".massu-memory-index.lock");
3853
4319
  }
3854
4320
  function withMemoryIndexLock(memoryDir, fn) {
3855
4321
  return withFileLockSync(memoryIndexLockPath(memoryDir), fn, {
@@ -3861,9 +4327,9 @@ function withMemoryIndexLock(memoryDir, fn) {
3861
4327
  });
3862
4328
  }
3863
4329
  function readMemoryIndex(indexPath) {
3864
- if (!existsSync9(indexPath)) return void 0;
4330
+ if (!existsSync11(indexPath)) return void 0;
3865
4331
  try {
3866
- return readFileSync6(indexPath, "utf8");
4332
+ return readFileSync7(indexPath, "utf8");
3867
4333
  } catch {
3868
4334
  return void 0;
3869
4335
  }
@@ -3899,12 +4365,24 @@ var init_memory_index_region = __esm({
3899
4365
  }
3900
4366
  });
3901
4367
 
4368
+ // src/memory-origin.ts
4369
+ function isLocalOrigin(o) {
4370
+ return o === LOCAL_ORIGIN;
4371
+ }
4372
+ var LOCAL_ORIGIN;
4373
+ var init_memory_origin = __esm({
4374
+ "src/memory-origin.ts"() {
4375
+ "use strict";
4376
+ LOCAL_ORIGIN = "local";
4377
+ }
4378
+ });
4379
+
3902
4380
  // src/rule-candidate-snapshot.ts
3903
- import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
4381
+ import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
3904
4382
  function takeSnapshots(paths) {
3905
4383
  const out = /* @__PURE__ */ new Map();
3906
4384
  for (const p of paths) {
3907
- if (existsSync10(p)) out.set(p, readFileSync7(p, "utf-8"));
4385
+ if (existsSync12(p)) out.set(p, readFileSync8(p, "utf-8"));
3908
4386
  else out.set(p, null);
3909
4387
  }
3910
4388
  return out;
@@ -3914,7 +4392,7 @@ function restoreSnapshots(snapshot) {
3914
4392
  for (const [path, content] of snapshot) {
3915
4393
  try {
3916
4394
  if (content === null) {
3917
- if (existsSync10(path)) unlinkSync3(path);
4395
+ if (existsSync12(path)) unlinkSync3(path);
3918
4396
  } else {
3919
4397
  writeFileSync5(path, content, "utf-8");
3920
4398
  }
@@ -3931,8 +4409,8 @@ var init_rule_candidate_snapshot = __esm({
3931
4409
  });
3932
4410
 
3933
4411
  // src/memory-file-ingest.ts
3934
- import { readFileSync as readFileSync9, existsSync as existsSync12, readdirSync as readdirSync2 } from "fs";
3935
- import { join as join6 } from "path";
4412
+ import { readFileSync as readFileSync10, existsSync as existsSync14, readdirSync as readdirSync2 } from "fs";
4413
+ import { join as join8 } from "path";
3936
4414
  import { parse as parseYaml2 } from "yaml";
3937
4415
  import { basename as pathBasename } from "path";
3938
4416
  import { createHash as createHash3 } from "crypto";
@@ -4038,8 +4516,8 @@ function upsertMemoryFileMirror(db, f) {
4038
4516
  return "inserted";
4039
4517
  }
4040
4518
  function ingestMemoryFile(db, sessionId, filePath) {
4041
- if (!existsSync12(filePath)) return "skipped";
4042
- const content = readFileSync9(filePath, "utf-8");
4519
+ if (!existsSync14(filePath)) return "skipped";
4520
+ const content = readFileSync10(filePath, "utf-8");
4043
4521
  if (Buffer.byteLength(content, "utf-8") > MAX_MEMORY_FILE_BYTES) {
4044
4522
  process.stderr.write(
4045
4523
  `[massu] memory file exceeds ${MAX_MEMORY_FILE_BYTES} bytes, not ingested: ${filePath}
@@ -4109,21 +4587,21 @@ ${body}` : body;
4109
4587
  }
4110
4588
  function backfillMemoryFiles(db, memoryDir, sessionId) {
4111
4589
  const stats = { inserted: 0, updated: 0, skipped: 0, total: 0 };
4112
- if (!existsSync12(memoryDir)) return stats;
4590
+ if (!existsSync14(memoryDir)) return stats;
4113
4591
  const files = readdirSync2(memoryDir).filter(
4114
4592
  (f) => f.endsWith(".md") && f !== "MEMORY.md"
4115
4593
  );
4116
4594
  stats.total = files.length;
4117
4595
  const sid = sessionId ?? `backfill-${Date.now()}`;
4118
4596
  for (const file of files) {
4119
- const result = ingestMemoryFile(db, sid, join6(memoryDir, file));
4597
+ const result = ingestMemoryFile(db, sid, join8(memoryDir, file));
4120
4598
  stats[result]++;
4121
4599
  }
4122
4600
  return stats;
4123
4601
  }
4124
4602
  function reconcileMemoryFileObservations(db, memoryDir, sessionId) {
4125
4603
  try {
4126
- if (!existsSync12(memoryDir)) return 0;
4604
+ if (!existsSync14(memoryDir)) return 0;
4127
4605
  let entries;
4128
4606
  try {
4129
4607
  entries = readdirSync2(memoryDir);
@@ -4135,7 +4613,7 @@ function reconcileMemoryFileObservations(db, memoryDir, sessionId) {
4135
4613
  for (const file of files) {
4136
4614
  liveNames.add(stripMdExtension(file));
4137
4615
  try {
4138
- const content = readFileSync9(join6(memoryDir, file), "utf-8");
4616
+ const content = readFileSync10(join8(memoryDir, file), "utf-8");
4139
4617
  const fm = readMemoryFileFrontmatter(content);
4140
4618
  if (fm && typeof fm.name === "string" && fm.name) liveNames.add(fm.name);
4141
4619
  } catch {
@@ -4289,18 +4767,18 @@ var init_memory_render_path = __esm({
4289
4767
  });
4290
4768
 
4291
4769
  // src/memory-tombstones.ts
4292
- import { readFileSync as readFileSync11, appendFileSync as appendFileSync2, existsSync as existsSync14 } from "fs";
4293
- import { join as join8 } from "path";
4770
+ import { readFileSync as readFileSync13, appendFileSync as appendFileSync3, existsSync as existsSync17 } from "fs";
4771
+ import { join as join11 } from "path";
4294
4772
  function tombstoneLedgerPath(memoryDir) {
4295
- return join8(memoryDir, TOMBSTONE_LEDGER);
4773
+ return join11(memoryDir, TOMBSTONE_LEDGER);
4296
4774
  }
4297
4775
  function readTombstones(memoryDir) {
4298
4776
  const out = /* @__PURE__ */ new Map();
4299
4777
  const p = tombstoneLedgerPath(memoryDir);
4300
- if (!existsSync14(p)) return out;
4778
+ if (!existsSync17(p)) return out;
4301
4779
  let raw;
4302
4780
  try {
4303
- raw = readFileSync11(p, "utf8");
4781
+ raw = readFileSync13(p, "utf8");
4304
4782
  } catch {
4305
4783
  return out;
4306
4784
  }
@@ -4368,26 +4846,26 @@ var init_memory_llm = __esm({
4368
4846
  // src/memory-backup.ts
4369
4847
  import {
4370
4848
  readdirSync as readdirSync3,
4371
- mkdirSync as mkdirSync9,
4849
+ mkdirSync as mkdirSync11,
4372
4850
  copyFileSync as copyFileSync2,
4373
4851
  statSync as statSync4,
4374
- existsSync as existsSync15,
4375
- rmSync as rmSync4,
4376
- readFileSync as readFileSync12
4852
+ existsSync as existsSync18,
4853
+ rmSync as rmSync5,
4854
+ readFileSync as readFileSync14
4377
4855
  } from "fs";
4378
- import { homedir as homedir6 } from "os";
4379
- import { join as join9, resolve as resolve7 } from "path";
4380
- function backupsRoot(home = homedir6()) {
4856
+ import { homedir as homedir10 } from "os";
4857
+ import { join as join12, resolve as resolve7 } from "path";
4858
+ function backupsRoot(home = homedir10()) {
4381
4859
  return resolve7(home, ".massu", "memory-backups");
4382
4860
  }
4383
4861
  function backupStamp2(nowMs) {
4384
4862
  return new Date(nowMs).toISOString().replace(/:/g, "-");
4385
4863
  }
4386
4864
  function newestMtimeMs(dir) {
4387
- if (!existsSync15(dir)) return 0;
4865
+ if (!existsSync18(dir)) return 0;
4388
4866
  let newest = 0;
4389
4867
  for (const entry of readdirSync3(dir)) {
4390
- const p = join9(dir, entry);
4868
+ const p = join12(dir, entry);
4391
4869
  try {
4392
4870
  const st = statSync4(p);
4393
4871
  if (st.isFile() && st.mtimeMs > newest) newest = st.mtimeMs;
@@ -4397,12 +4875,12 @@ function newestMtimeMs(dir) {
4397
4875
  }
4398
4876
  return newest;
4399
4877
  }
4400
- function listBackups(home = homedir6()) {
4878
+ function listBackups(home = homedir10()) {
4401
4879
  const root = backupsRoot(home);
4402
- if (!existsSync15(root)) return [];
4880
+ if (!existsSync18(root)) return [];
4403
4881
  const out = [];
4404
4882
  for (const stamp of readdirSync3(root)) {
4405
- const dir = join9(root, stamp);
4883
+ const dir = join12(root, stamp);
4406
4884
  try {
4407
4885
  const st = statSync4(dir);
4408
4886
  if (!st.isDirectory()) continue;
@@ -4413,28 +4891,28 @@ function listBackups(home = homedir6()) {
4413
4891
  }
4414
4892
  return out.sort((a, b) => b.createdMs - a.createdMs);
4415
4893
  }
4416
- function hasFreshBackup(memoryDir, home = homedir6()) {
4894
+ function hasFreshBackup(memoryDir, home = homedir10()) {
4417
4895
  const backups = listBackups(home);
4418
4896
  if (backups.length === 0) return false;
4419
4897
  const newestChange = newestMtimeMs(memoryDir);
4420
4898
  return backups[0].createdMs >= newestChange;
4421
4899
  }
4422
- function takeBackup(memoryDir, nowMs, home = homedir6(), retention = DEFAULT_RETENTION2) {
4423
- if (!existsSync15(memoryDir)) {
4900
+ function takeBackup(memoryDir, nowMs, home = homedir10(), retention = DEFAULT_RETENTION2) {
4901
+ if (!existsSync18(memoryDir)) {
4424
4902
  throw new BackupError(`memory directory does not exist: ${memoryDir}`);
4425
4903
  }
4426
4904
  const stamp = backupStamp2(nowMs);
4427
- const dir = join9(backupsRoot(home), stamp);
4905
+ const dir = join12(backupsRoot(home), stamp);
4428
4906
  try {
4429
- mkdirSync9(dir, { recursive: true, mode: 448 });
4907
+ mkdirSync11(dir, { recursive: true, mode: 448 });
4430
4908
  for (const entry of readdirSync3(memoryDir)) {
4431
- const srcPath = join9(memoryDir, entry);
4909
+ const srcPath = join12(memoryDir, entry);
4432
4910
  try {
4433
4911
  if (!statSync4(srcPath).isFile()) continue;
4434
4912
  } catch {
4435
4913
  continue;
4436
4914
  }
4437
- copyFileSync2(srcPath, join9(dir, entry));
4915
+ copyFileSync2(srcPath, join12(dir, entry));
4438
4916
  }
4439
4917
  } catch (err) {
4440
4918
  throw new BackupError(`backup failed: ${err.message}`);
@@ -4442,11 +4920,11 @@ function takeBackup(memoryDir, nowMs, home = homedir6(), retention = DEFAULT_RET
4442
4920
  pruneBackups(home, retention);
4443
4921
  return { stamp, dir, createdMs: nowMs };
4444
4922
  }
4445
- function pruneBackups(home = homedir6(), retention = DEFAULT_RETENTION2) {
4923
+ function pruneBackups(home = homedir10(), retention = DEFAULT_RETENTION2) {
4446
4924
  const backups = listBackups(home);
4447
4925
  for (const stale of backups.slice(Math.max(retention, 1))) {
4448
4926
  try {
4449
- rmSync4(stale.dir, { recursive: true, force: true });
4927
+ rmSync5(stale.dir, { recursive: true, force: true });
4450
4928
  } catch {
4451
4929
  }
4452
4930
  }
@@ -4499,9 +4977,9 @@ __export(memory_renderer_exports, {
4499
4977
  renderMemoryFiles: () => renderMemoryFiles,
4500
4978
  stripFrontmatter: () => stripFrontmatter
4501
4979
  });
4502
- import { existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
4503
- import { join as join10 } from "path";
4504
- import { homedir as homedir7 } from "os";
4980
+ import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
4981
+ import { join as join13 } from "path";
4982
+ import { homedir as homedir11 } from "os";
4505
4983
  import { createHash as createHash5 } from "crypto";
4506
4984
  function renderMemoryFiles(db, candidates, opts) {
4507
4985
  const config = opts.config ?? resolveMemoryFilesConfig();
@@ -4510,11 +4988,11 @@ function renderMemoryFiles(db, candidates, opts) {
4510
4988
  }
4511
4989
  const dryRun = opts.dryRun === true;
4512
4990
  const now = opts.now ?? Date.now();
4513
- const home = opts.home ?? homedir7();
4991
+ const home = opts.home ?? homedir11();
4514
4992
  const audit = opts.audit ?? (() => {
4515
4993
  });
4516
4994
  const { memoryDir } = opts;
4517
- if (!existsSync16(memoryDir)) {
4995
+ if (!existsSync19(memoryDir)) {
4518
4996
  return EMPTY("no_memory_dir");
4519
4997
  }
4520
4998
  try {
@@ -4542,7 +5020,7 @@ function renderLocked(db, candidates, o) {
4542
5020
  const ownerOf = relPathOwnerLookup(db);
4543
5021
  const planned = [];
4544
5022
  for (const c of candidates) {
4545
- if (c.origin !== "local") {
5023
+ if (!isLocalOrigin(c.origin)) {
4546
5024
  refusals.push({ name: c.name, reason: "non_local_origin", detail: c.origin || "unknown" });
4547
5025
  audit("memory_file_render_refused", { name: c.name, reason: "non_local_origin" });
4548
5026
  continue;
@@ -4582,8 +5060,8 @@ function renderLocked(db, candidates, o) {
4582
5060
  refusals.push({ name: c.name, reason: "tombstoned" });
4583
5061
  continue;
4584
5062
  }
4585
- if (existsSync16(absPath)) {
4586
- const existing = readFileSync13(absPath, "utf8");
5063
+ if (existsSync19(absPath)) {
5064
+ const existing = readFileSync15(absPath, "utf8");
4587
5065
  const fm = readMemoryFileFrontmatter(existing);
4588
5066
  const storeRow = db.prepare(
4589
5067
  `SELECT massu_authored, massu_render_mac, adopted_human_at_epoch
@@ -4632,7 +5110,7 @@ function renderLocked(db, candidates, o) {
4632
5110
  return EMPTY("backup_failed");
4633
5111
  }
4634
5112
  }
4635
- const indexPath = o.indexPath ?? join10(memoryDir, "MEMORY.md");
5113
+ const indexPath = o.indexPath ?? join13(memoryDir, "MEMORY.md");
4636
5114
  const targets = [...planned.map((p) => p.absPath), indexPath];
4637
5115
  const snapshot = takeSnapshots(targets);
4638
5116
  let bytesWritten = 0;
@@ -4786,6 +5264,7 @@ var init_memory_renderer = __esm({
4786
5264
  init_memory_authorship();
4787
5265
  init_memory_render_path();
4788
5266
  init_memory_tombstones();
5267
+ init_memory_origin();
4789
5268
  init_memory_llm();
4790
5269
  init_memory_backup();
4791
5270
  init_memory_index_region();
@@ -4806,18 +5285,21 @@ var init_memory_renderer = __esm({
4806
5285
  // src/memory-render-candidates.ts
4807
5286
  var memory_render_candidates_exports = {};
4808
5287
  __export(memory_render_candidates_exports, {
5288
+ RENDERABLE_MEMORY_TYPES: () => RENDERABLE_MEMORY_TYPES,
4809
5289
  loadRenderCandidates: () => loadRenderCandidates
4810
5290
  });
4811
5291
  function loadRenderCandidates(db) {
4812
5292
  const cfg = resolveMemoryFilesConfig();
5293
+ const typePlaceholders = RENDERABLE_MEMORY_TYPES.map(() => "?").join(", ");
4813
5294
  const rows = db.prepare(
4814
5295
  `SELECT id, title, detail, importance, COALESCE(origin, 'local') AS origin
4815
5296
  FROM observations
4816
5297
  WHERE importance >= ?
4817
5298
  AND COALESCE(expired_at_epoch, 0) = 0
5299
+ AND type IN (${typePlaceholders})
4818
5300
  ORDER BY importance DESC, created_at_epoch DESC
4819
5301
  LIMIT 50`
4820
- ).all(cfg.renderMinImportance);
5302
+ ).all(cfg.renderMinImportance, ...RENDERABLE_MEMORY_TYPES);
4821
5303
  return rows.filter((r) => !r.title.startsWith("[memory-file]")).map((r) => ({
4822
5304
  observationId: r.id,
4823
5305
  name: r.title,
@@ -4827,10 +5309,17 @@ function loadRenderCandidates(db) {
4827
5309
  origin: r.origin
4828
5310
  }));
4829
5311
  }
5312
+ var RENDERABLE_MEMORY_TYPES;
4830
5313
  var init_memory_render_candidates = __esm({
4831
5314
  "src/memory-render-candidates.ts"() {
4832
5315
  "use strict";
4833
5316
  init_memory_files_config();
5317
+ RENDERABLE_MEMORY_TYPES = [
5318
+ "decision",
5319
+ "failed_attempt",
5320
+ "incident_near_miss",
5321
+ "cr_violation"
5322
+ ];
4834
5323
  }
4835
5324
  });
4836
5325
 
@@ -4996,17 +5485,17 @@ var init_manifest_registry = __esm({
4996
5485
  });
4997
5486
 
4998
5487
  // src/detect/package-detector.ts
4999
- import { readFileSync as readFileSync14, existsSync as existsSync17, statSync as statSync5, lstatSync, readdirSync as readdirSync4 } from "fs";
5000
- import { join as join11, relative as relative2 } from "path";
5488
+ import { readFileSync as readFileSync16, existsSync as existsSync20, statSync as statSync5, lstatSync, readdirSync as readdirSync4 } from "fs";
5489
+ import { join as join14, relative as relative2 } from "path";
5001
5490
  import { parse as parseToml } from "smol-toml";
5002
5491
  function safeRead(path) {
5003
5492
  try {
5004
- if (!existsSync17(path)) return null;
5493
+ if (!existsSync20(path)) return null;
5005
5494
  const ls = lstatSync(path);
5006
5495
  if (ls.isSymbolicLink()) return null;
5007
5496
  const st = statSync5(path);
5008
5497
  if (!st.isFile()) return null;
5009
- return readFileSync14(path, "utf-8");
5498
+ return readFileSync16(path, "utf-8");
5010
5499
  } catch {
5011
5500
  return null;
5012
5501
  }
@@ -5037,7 +5526,7 @@ function parsePackageJson(path, directory, root, warnings) {
5037
5526
  const peer = Object.keys(
5038
5527
  pkg.peerDependencies ?? {}
5039
5528
  );
5040
- const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync17(join11(directory, "tsconfig.json"));
5529
+ const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync20(join14(directory, "tsconfig.json"));
5041
5530
  const language = hasTs ? "typescript" : "javascript";
5042
5531
  const scripts = Object.keys(
5043
5532
  pkg.scripts ?? {}
@@ -5463,8 +5952,8 @@ function detectManifestsInDir(dir, root, warnings) {
5463
5952
  let dirEntries = null;
5464
5953
  for (const entry of getManifestRegistry2()) {
5465
5954
  if (!entry.pattern.startsWith("*")) {
5466
- const path = join11(dir, entry.pattern);
5467
- if (!existsSync17(path)) continue;
5955
+ const path = join14(dir, entry.pattern);
5956
+ if (!existsSync20(path)) continue;
5468
5957
  const m = entry.parse(path, dir, root, warnings);
5469
5958
  if (m !== null) out.push(m);
5470
5959
  } else {
@@ -5477,8 +5966,8 @@ function detectManifestsInDir(dir, root, warnings) {
5477
5966
  }
5478
5967
  for (const fname of dirEntries) {
5479
5968
  if (!matchManifestPattern2(fname, entry.pattern)) continue;
5480
- const path = join11(dir, fname);
5481
- if (!existsSync17(path)) continue;
5969
+ const path = join14(dir, fname);
5970
+ if (!existsSync20(path)) continue;
5482
5971
  const m = entry.parse(path, dir, root, warnings);
5483
5972
  if (m !== null) out.push(m);
5484
5973
  }
@@ -5488,7 +5977,7 @@ function detectManifestsInDir(dir, root, warnings) {
5488
5977
  }
5489
5978
  function listSubdirs(dir) {
5490
5979
  try {
5491
- return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) => join11(dir, e.name));
5980
+ return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) => join14(dir, e.name));
5492
5981
  } catch {
5493
5982
  return [];
5494
5983
  }
@@ -5498,8 +5987,8 @@ function detectPackageManifests(projectRoot) {
5498
5987
  const manifests = [];
5499
5988
  manifests.push(...detectManifestsInDir(projectRoot, projectRoot, warnings));
5500
5989
  for (const ws of WORKSPACE_DIRS) {
5501
- const wsRoot = join11(projectRoot, ws);
5502
- if (!existsSync17(wsRoot)) continue;
5990
+ const wsRoot = join14(projectRoot, ws);
5991
+ if (!existsSync20(wsRoot)) continue;
5503
5992
  for (const sub of listSubdirs(wsRoot)) {
5504
5993
  manifests.push(...detectManifestsInDir(sub, projectRoot, warnings));
5505
5994
  for (const sub2 of listSubdirs(sub)) {
@@ -11678,38 +12167,38 @@ var init_source_dir_detector = __esm({
11678
12167
  });
11679
12168
 
11680
12169
  // src/detect/monorepo-detector.ts
11681
- import { readFileSync as readFileSync15, existsSync as existsSync18, statSync as statSync6, lstatSync as lstatSync2, readdirSync as readdirSync5 } from "fs";
11682
- import { join as join12, relative as relative3 } from "path";
12170
+ import { readFileSync as readFileSync17, existsSync as existsSync21, statSync as statSync6, lstatSync as lstatSync2, readdirSync as readdirSync5 } from "fs";
12171
+ import { join as join15, relative as relative3 } from "path";
11683
12172
  import { parse as parseYaml3 } from "yaml";
11684
12173
  import { parse as parseToml2 } from "smol-toml";
11685
12174
  function safeReadText(path) {
11686
12175
  try {
11687
- if (!existsSync18(path)) return null;
12176
+ if (!existsSync21(path)) return null;
11688
12177
  const ls = lstatSync2(path);
11689
12178
  if (ls.isSymbolicLink()) return null;
11690
12179
  const st = statSync6(path);
11691
12180
  if (!st.isFile()) return null;
11692
- return readFileSync15(path, "utf-8");
12181
+ return readFileSync17(path, "utf-8");
11693
12182
  } catch {
11694
12183
  return null;
11695
12184
  }
11696
12185
  }
11697
12186
  function firstManifestIn(dir) {
11698
12187
  for (const m of MANIFEST_PRIORITY) {
11699
- if (existsSync18(join12(dir, m))) return m;
12188
+ if (existsSync21(join15(dir, m))) return m;
11700
12189
  }
11701
12190
  return null;
11702
12191
  }
11703
12192
  function manifestName(dir, manifest) {
11704
12193
  try {
11705
12194
  if (manifest === "package.json") {
11706
- const raw = safeReadText(join12(dir, "package.json"));
12195
+ const raw = safeReadText(join15(dir, "package.json"));
11707
12196
  if (!raw) return null;
11708
12197
  const pkg = JSON.parse(raw);
11709
12198
  return typeof pkg.name === "string" ? pkg.name : null;
11710
12199
  }
11711
12200
  if (manifest === "pyproject.toml") {
11712
- const raw = safeReadText(join12(dir, "pyproject.toml"));
12201
+ const raw = safeReadText(join15(dir, "pyproject.toml"));
11713
12202
  if (!raw) return null;
11714
12203
  const toml = parseToml2(raw);
11715
12204
  const project = toml.project;
@@ -11720,7 +12209,7 @@ function manifestName(dir, manifest) {
11720
12209
  return null;
11721
12210
  }
11722
12211
  if (manifest === "Cargo.toml") {
11723
- const raw = safeReadText(join12(dir, "Cargo.toml"));
12212
+ const raw = safeReadText(join15(dir, "Cargo.toml"));
11724
12213
  if (!raw) return null;
11725
12214
  const toml = parseToml2(raw);
11726
12215
  const pkg = toml.package;
@@ -11728,7 +12217,7 @@ function manifestName(dir, manifest) {
11728
12217
  return null;
11729
12218
  }
11730
12219
  if (manifest === "go.mod") {
11731
- const raw = safeReadText(join12(dir, "go.mod"));
12220
+ const raw = safeReadText(join15(dir, "go.mod"));
11732
12221
  if (!raw) return null;
11733
12222
  for (const line of raw.split(/\r?\n/)) {
11734
12223
  const trimmed = line.trim();
@@ -11752,7 +12241,7 @@ function pkgFromDir(root, dir) {
11752
12241
  }
11753
12242
  function listSubdirs2(dir) {
11754
12243
  try {
11755
- return readdirSync5(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) => join12(dir, e.name));
12244
+ return readdirSync5(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) => join15(dir, e.name));
11756
12245
  } catch {
11757
12246
  return [];
11758
12247
  }
@@ -11760,8 +12249,8 @@ function listSubdirs2(dir) {
11760
12249
  function genericWorkspaces(root) {
11761
12250
  const out = [];
11762
12251
  for (const parent of CONVENTIONAL_WORKSPACE_PARENTS) {
11763
- const p = join12(root, parent);
11764
- if (!existsSync18(p)) continue;
12252
+ const p = join15(root, parent);
12253
+ if (!existsSync21(p)) continue;
11765
12254
  for (const sub of listSubdirs2(p)) {
11766
12255
  const pkg = pkgFromDir(root, sub);
11767
12256
  if (pkg) out.push(pkg);
@@ -11770,7 +12259,7 @@ function genericWorkspaces(root) {
11770
12259
  return out;
11771
12260
  }
11772
12261
  function detectYarnWorkspaces(root) {
11773
- const raw = safeReadText(join12(root, "package.json"));
12262
+ const raw = safeReadText(join15(root, "package.json"));
11774
12263
  if (!raw) return null;
11775
12264
  let pkg;
11776
12265
  try {
@@ -11785,7 +12274,7 @@ function detectYarnWorkspaces(root) {
11785
12274
  return expandWorkspaceGlobs(root, globs);
11786
12275
  }
11787
12276
  function detectPnpmWorkspaces(root) {
11788
- const raw = safeReadText(join12(root, "pnpm-workspace.yaml"));
12277
+ const raw = safeReadText(join15(root, "pnpm-workspace.yaml"));
11789
12278
  if (!raw) return null;
11790
12279
  try {
11791
12280
  const parsed = parseYaml3(raw);
@@ -11801,8 +12290,8 @@ function expandWorkspaceGlobs(root, globs) {
11801
12290
  for (const pattern of globs) {
11802
12291
  const parts = pattern.split("/");
11803
12292
  if (parts.length === 2 && (parts[1] === "*" || parts[1] === "**")) {
11804
- const parent = join12(root, parts[0]);
11805
- if (!existsSync18(parent)) continue;
12293
+ const parent = join15(root, parts[0]);
12294
+ if (!existsSync21(parent)) continue;
11806
12295
  for (const sub of listSubdirs2(parent)) {
11807
12296
  const pkg = pkgFromDir(root, sub);
11808
12297
  if (pkg && !seen.has(pkg.path)) {
@@ -11812,8 +12301,8 @@ function expandWorkspaceGlobs(root, globs) {
11812
12301
  }
11813
12302
  continue;
11814
12303
  }
11815
- const direct = join12(root, pattern);
11816
- if (existsSync18(direct)) {
12304
+ const direct = join15(root, pattern);
12305
+ if (existsSync21(direct)) {
11817
12306
  const pkg = pkgFromDir(root, direct);
11818
12307
  if (pkg && !seen.has(pkg.path)) {
11819
12308
  seen.add(pkg.path);
@@ -11824,16 +12313,16 @@ function expandWorkspaceGlobs(root, globs) {
11824
12313
  return out;
11825
12314
  }
11826
12315
  function hasTurbo(root) {
11827
- return existsSync18(join12(root, "turbo.json"));
12316
+ return existsSync21(join15(root, "turbo.json"));
11828
12317
  }
11829
12318
  function hasNx(root) {
11830
- return existsSync18(join12(root, "nx.json"));
12319
+ return existsSync21(join15(root, "nx.json"));
11831
12320
  }
11832
12321
  function hasLerna(root) {
11833
- return existsSync18(join12(root, "lerna.json"));
12322
+ return existsSync21(join15(root, "lerna.json"));
11834
12323
  }
11835
12324
  function hasBazel(root) {
11836
- return existsSync18(join12(root, "WORKSPACE")) || existsSync18(join12(root, "WORKSPACE.bazel")) || existsSync18(join12(root, "MODULE.bazel"));
12325
+ return existsSync21(join15(root, "WORKSPACE")) || existsSync21(join15(root, "WORKSPACE.bazel")) || existsSync21(join15(root, "MODULE.bazel"));
11837
12326
  }
11838
12327
  function detectMonorepo(projectRoot) {
11839
12328
  const nested = [];
@@ -12024,8 +12513,8 @@ var init_vr_command_map = __esm({
12024
12513
  });
12025
12514
 
12026
12515
  // src/detect/domain-inferrer.ts
12027
- import { existsSync as existsSync19, readdirSync as readdirSync6 } from "fs";
12028
- import { join as join13 } from "path";
12516
+ import { existsSync as existsSync22, readdirSync as readdirSync6 } from "fs";
12517
+ import { join as join16 } from "path";
12029
12518
  function titleCase(s) {
12030
12519
  if (!s) return s;
12031
12520
  return s.split(/[-_\s]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
@@ -12045,8 +12534,8 @@ function topLevelSrcSubdirs(root, sourceDirs) {
12045
12534
  const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
12046
12535
  const seen = /* @__PURE__ */ new Set();
12047
12536
  for (const rel of effective) {
12048
- const abs = join13(root, rel);
12049
- if (!existsSync19(abs)) continue;
12537
+ const abs = join16(root, rel);
12538
+ if (!existsSync22(abs)) continue;
12050
12539
  try {
12051
12540
  for (const e of readdirSync6(abs, { withFileTypes: true })) {
12052
12541
  if (!e.isDirectory()) continue;
@@ -12133,8 +12622,8 @@ var init_domain_inferrer = __esm({
12133
12622
  });
12134
12623
 
12135
12624
  // src/detect/regex-fallback.ts
12136
- import { existsSync as existsSync20, readdirSync as readdirSync7, readFileSync as readFileSync16, statSync as statSync7 } from "fs";
12137
- import { resolve as resolve8, join as join14, basename as basename4 } from "path";
12625
+ import { existsSync as existsSync23, readdirSync as readdirSync7, readFileSync as readFileSync18, statSync as statSync7 } from "fs";
12626
+ import { resolve as resolve8, join as join17, basename as basename4 } from "path";
12138
12627
  function introspectPython(detection, projectRoot) {
12139
12628
  const sourceDir = resolveSourceDir(detection, "python", projectRoot);
12140
12629
  if (!sourceDir) return null;
@@ -12301,9 +12790,9 @@ function resolveSourceDir(detection, lang, projectRoot) {
12301
12790
  if (list.length > 0) {
12302
12791
  const first = list[0];
12303
12792
  const abs = resolve8(projectRoot, first);
12304
- return existsSync20(abs) ? abs : null;
12793
+ return existsSync23(abs) ? abs : null;
12305
12794
  }
12306
- return existsSync20(projectRoot) ? projectRoot : null;
12795
+ return existsSync23(projectRoot) ? projectRoot : null;
12307
12796
  }
12308
12797
  function sampleFiles(dir, nameRegex, pathFilter) {
12309
12798
  const out = [];
@@ -12323,7 +12812,7 @@ function sampleFiles(dir, nameRegex, pathFilter) {
12323
12812
  if (entry === "__pycache__") continue;
12324
12813
  if (entry === "venv" || entry === ".venv") continue;
12325
12814
  if (entry === "dist" || entry === "build") continue;
12326
- const child = join14(path, entry);
12815
+ const child = join17(path, entry);
12327
12816
  let st;
12328
12817
  try {
12329
12818
  st = statSync7(child);
@@ -12348,7 +12837,7 @@ function readSafe(path) {
12348
12837
  try {
12349
12838
  const st = statSync7(path);
12350
12839
  if (st.size > MAX_FILE_BYTES) return null;
12351
- return readFileSync16(path, "utf-8");
12840
+ return readFileSync18(path, "utf-8");
12352
12841
  } catch {
12353
12842
  return null;
12354
12843
  }
@@ -12676,9 +13165,9 @@ function detectDrift(currentConfig, actualDetection) {
12676
13165
  }
12677
13166
  const detectedManifestPaths = new Set(actualDetection.manifests.map((m) => m.relativePath));
12678
13167
  const declaredManifestPaths = /* @__PURE__ */ new Set();
12679
- const canonical = currentConfig.canonical_paths;
12680
- if (canonical && typeof canonical.manifest_paths === "string") {
12681
- for (const p of canonical.manifest_paths.split(",").map((s) => s.trim())) {
13168
+ const canonical2 = currentConfig.canonical_paths;
13169
+ if (canonical2 && typeof canonical2.manifest_paths === "string") {
13170
+ for (const p of canonical2.manifest_paths.split(",").map((s) => s.trim())) {
12682
13171
  if (p) declaredManifestPaths.add(p);
12683
13172
  }
12684
13173
  }
@@ -12725,10 +13214,34 @@ var init_drift = __esm({
12725
13214
  // src/hooks/session-start.ts
12726
13215
  init_memory_db();
12727
13216
 
13217
+ // src/team-rule-sync.ts
13218
+ import { existsSync as existsSync13, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync8 } from "fs";
13219
+ import { homedir as homedir6 } from "os";
13220
+ import { join as join7, dirname as dirname10 } from "path";
13221
+
13222
+ // src/security/promotion-apply-mac.ts
13223
+ init_memory_authorship();
13224
+ import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
13225
+ import { homedir as homedir5 } from "os";
13226
+ var DOMAIN = "massu.promotion-apply.v1";
13227
+ function canonical(f) {
13228
+ return [DOMAIN, f.origin, f.org_id, f.prompt_hash, f.destination, f.draft_text].join("\n");
13229
+ }
13230
+ function deriveApplyKey(renderKey) {
13231
+ return createHmac2("sha256", renderKey).update(DOMAIN, "utf8").digest();
13232
+ }
13233
+ function computePromotionApplyMac(fields, home = homedir5()) {
13234
+ try {
13235
+ const key = ensureRenderKey(home);
13236
+ if (!key) return null;
13237
+ return createHmac2("sha256", deriveApplyKey(key)).update(canonical(fields), "utf8").digest("hex");
13238
+ } catch {
13239
+ return null;
13240
+ }
13241
+ }
13242
+
12728
13243
  // src/team-rule-sync.ts
12729
13244
  init_config();
12730
- import { existsSync as existsSync11, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync7 } from "fs";
12731
- import { join as join5, dirname as dirname9 } from "path";
12732
13245
 
12733
13246
  // src/license.ts
12734
13247
  init_config();
@@ -12779,13 +13292,13 @@ function verifyEd25519SignedEnvelope(key, payload) {
12779
13292
  if (typeof k !== "string") continue;
12780
13293
  canonicalObj[k] = payload[k];
12781
13294
  }
12782
- const canonical = JSON.stringify(canonicalObj, [...payloadKeys].sort());
13295
+ const canonical2 = JSON.stringify(canonicalObj, [...payloadKeys].sort());
12783
13296
  try {
12784
13297
  const der = Buffer.concat([SPKI_ED25519_PREFIX, Buffer.from(key.pubkeyBytes)]);
12785
13298
  const pubkey = createPublicKey({ key: der, format: "der", type: "spki" });
12786
13299
  const ok = cryptoVerify(
12787
13300
  null,
12788
- Buffer.from(canonical, "utf-8"),
13301
+ Buffer.from(canonical2, "utf-8"),
12789
13302
  pubkey,
12790
13303
  Buffer.from(sig, "base64")
12791
13304
  );
@@ -12998,6 +13511,7 @@ init_config();
12998
13511
 
12999
13512
  // src/rule-candidate-applier.ts
13000
13513
  init_memory_db();
13514
+ init_memory_origin();
13001
13515
 
13002
13516
  // src/rule-candidate-funnel.ts
13003
13517
  init_memory_db();
@@ -13125,11 +13639,11 @@ async function pullTeamPromotions(db, opts = {}) {
13125
13639
  result.revoked_handled += 1;
13126
13640
  continue;
13127
13641
  }
13128
- if (existsSync11(candidatePath) || alreadyApplied(db, p.prompt_hash)) {
13642
+ if (existsSync13(candidatePath) || alreadyApplied(db, p.prompt_hash)) {
13129
13643
  result.skipped += 1;
13130
13644
  continue;
13131
13645
  }
13132
- materializeCandidate(db, projectRoot, candidatePath, p, signedOrgId);
13646
+ materializeCandidate(db, projectRoot, candidatePath, p, signedOrgId, opts.home ?? homedir6());
13133
13647
  result.materialized += 1;
13134
13648
  }
13135
13649
  const serverCursor = typeof envelope.cursor === "number" ? envelope.cursor : 0;
@@ -13161,7 +13675,7 @@ function isValidWirePromotion(p) {
13161
13675
  return typeof r.prompt_hash === "string" && PROMPT_HASH_RE.test(r.prompt_hash) && typeof r.destination === "string" && typeof r.draft_text === "string" && typeof r.promoted_by === "string" && typeof r.promoted_at === "string";
13162
13676
  }
13163
13677
  function sidecarPath(projectRoot, promptHash) {
13164
- return join5(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
13678
+ return join7(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
13165
13679
  }
13166
13680
  function alreadyApplied(db, promptHash) {
13167
13681
  try {
@@ -13175,7 +13689,7 @@ function alreadyApplied(db, promptHash) {
13175
13689
  }
13176
13690
  }
13177
13691
  function handleRevocation(db, projectRoot, candidatePath, promptHash) {
13178
- if (existsSync11(candidatePath)) {
13692
+ if (existsSync13(candidatePath)) {
13179
13693
  try {
13180
13694
  unlinkSync4(candidatePath);
13181
13695
  } catch {
@@ -13189,8 +13703,12 @@ function handleRevocation(db, projectRoot, candidatePath, promptHash) {
13189
13703
  );
13190
13704
  }
13191
13705
  }
13192
- function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
13706
+ function materializeCandidate(db, projectRoot, candidatePath, p, orgId, home) {
13193
13707
  const promptText = p.draft_text.replace(/\n+/g, " ").slice(0, 200) || `team rule ${p.prompt_hash}`;
13708
+ const applyMac = computePromotionApplyMac(
13709
+ { origin: "team", org_id: orgId, prompt_hash: p.prompt_hash, destination: p.destination, draft_text: p.draft_text },
13710
+ home
13711
+ );
13194
13712
  const sidecar = {
13195
13713
  // Standard RuleCandidatePayload fields (so `/massu-rule approve` → readCandidate
13196
13714
  // → validateCandidatePayload passes), synthesized from the promotion.
@@ -13213,6 +13731,7 @@ function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
13213
13731
  promoted_by: p.promoted_by,
13214
13732
  promoted_at: p.promoted_at,
13215
13733
  signature_verified: true,
13734
+ ...applyMac ? { apply_mac: applyMac } : {},
13216
13735
  ...p.hardened === true ? { hardened: true } : {}
13217
13736
  },
13218
13737
  // Extra fields the `/massu-rule approve` flow reads to drive the apply (the
@@ -13223,8 +13742,8 @@ function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
13223
13742
  draft_text: p.draft_text,
13224
13743
  ...p.review_attestation !== void 0 ? { publisher_review_attestation: p.review_attestation } : {}
13225
13744
  };
13226
- const dir = dirname9(candidatePath);
13227
- if (!existsSync11(dir)) mkdirSync7(dir, { recursive: true });
13745
+ const dir = dirname10(candidatePath);
13746
+ if (!existsSync13(dir)) mkdirSync8(dir, { recursive: true });
13228
13747
  writeFileSync6(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
13229
13748
  try {
13230
13749
  shareObservation(db, p.promoted_by, getProjectName(), "rule_promotion", promptText, {
@@ -13279,23 +13798,24 @@ function failSync(db, reason) {
13279
13798
  // src/hooks/session-start.ts
13280
13799
  init_memory_file_ingest();
13281
13800
  init_config();
13282
- import { readFileSync as readFileSync17, existsSync as existsSync21 } from "fs";
13283
- import { join as join15, resolve as resolve9 } from "path";
13801
+ init_db_driver();
13802
+ import { readFileSync as readFileSync19, existsSync as existsSync24 } from "fs";
13803
+ import { join as join18, resolve as resolve9 } from "path";
13284
13804
  import { parse as parseYaml4 } from "yaml";
13285
13805
 
13286
13806
  // src/capability-advisor.ts
13287
13807
  import { createHash as createHash4 } from "crypto";
13288
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
13289
- import { homedir as homedir5 } from "os";
13290
- import { dirname as dirname10, join as join7 } from "path";
13291
- function advisorStatePath(home = homedir5()) {
13292
- return join7(home, ".massu", "advisor-state.json");
13808
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
13809
+ import { homedir as homedir7 } from "os";
13810
+ import { dirname as dirname11, join as join9 } from "path";
13811
+ function advisorStatePath(home = homedir7()) {
13812
+ return join9(home, ".massu", "advisor-state.json");
13293
13813
  }
13294
13814
  function readAdvisorState(home) {
13295
13815
  try {
13296
13816
  const p = advisorStatePath(home);
13297
- if (!existsSync13(p)) return {};
13298
- return JSON.parse(readFileSync10(p, "utf-8"));
13817
+ if (!existsSync15(p)) return {};
13818
+ return JSON.parse(readFileSync11(p, "utf-8"));
13299
13819
  } catch {
13300
13820
  return {};
13301
13821
  }
@@ -13303,7 +13823,7 @@ function readAdvisorState(home) {
13303
13823
  function writeAdvisorState(state, home) {
13304
13824
  try {
13305
13825
  const p = advisorStatePath(home);
13306
- mkdirSync8(dirname10(p), { recursive: true });
13826
+ mkdirSync9(dirname11(p), { recursive: true });
13307
13827
  writeFileSync7(p, JSON.stringify(state, null, 2), { mode: 384 });
13308
13828
  } catch {
13309
13829
  }
@@ -13453,11 +13973,85 @@ var localModelAdvisor = {
13453
13973
  }
13454
13974
  };
13455
13975
 
13976
+ // src/advisors/cross-repo-share-advisor.ts
13977
+ init_config();
13978
+ import { homedir as homedir9 } from "os";
13979
+
13980
+ // src/memory-repos-registry.ts
13981
+ import {
13982
+ chmodSync as chmodSync5,
13983
+ existsSync as existsSync16,
13984
+ mkdirSync as mkdirSync10,
13985
+ readFileSync as readFileSync12,
13986
+ writeFileSync as writeFileSync8
13987
+ } from "fs";
13988
+ import { homedir as homedir8 } from "os";
13989
+ import { dirname as dirname12, join as join10 } from "path";
13990
+ var REPOS_REGISTRY_VERSION = 1;
13991
+ function reposRegistryPath(home = homedir8()) {
13992
+ return join10(home, ".massu", "repos.json");
13993
+ }
13994
+ function emptyRegistry() {
13995
+ return { version: REPOS_REGISTRY_VERSION, repos: [] };
13996
+ }
13997
+ function readReposRegistry(home = homedir8()) {
13998
+ const p = reposRegistryPath(home);
13999
+ if (!existsSync16(p)) return emptyRegistry();
14000
+ try {
14001
+ const parsed = JSON.parse(readFileSync12(p, "utf-8"));
14002
+ if (!parsed || typeof parsed !== "object") return emptyRegistry();
14003
+ const r = parsed;
14004
+ if (!Array.isArray(r.repos)) return emptyRegistry();
14005
+ const repos = r.repos.filter(
14006
+ (e) => !!e && typeof e === "object" && typeof e.repo_id === "string" && typeof e.label === "string"
14007
+ );
14008
+ return { version: typeof r.version === "number" ? r.version : REPOS_REGISTRY_VERSION, repos };
14009
+ } catch {
14010
+ return emptyRegistry();
14011
+ }
14012
+ }
14013
+
14014
+ // src/advisors/cross-repo-share-advisor.ts
14015
+ var CROSS_REPO_SHARE_ADVISOR_ID = "cross-repo-sharing";
14016
+ var CROSS_REPO_SHARE_REMEDY_KEYS = ["memory.share.enabled", "memory.share.subscribe"];
14017
+ function detectShareableRepos(home) {
14018
+ const repos = readReposRegistry(home).repos.filter((r) => r.share_enabled);
14019
+ if (repos.length === 0) return null;
14020
+ const labels = repos.map((r) => r.label);
14021
+ return {
14022
+ // Fingerprint covers the set of shareable repos, so registering a NEW repo
14023
+ // re-triggers the offer (the operator's setup changed).
14024
+ fingerprint: fingerprintOf(repos.map((r) => r.repo_id)),
14025
+ render: () => renderOffer(labels)
14026
+ };
14027
+ }
14028
+ function renderOffer(repoLabels) {
14029
+ const others = repoLabels.length === 1 ? `\`${repoLabels[0]}\`` : `${repoLabels.length} of your repos`;
14030
+ return [
14031
+ "\u{1F4A1} Massu can surface a decision from one of your repos in another \u2014 off by default, local-only, zero-network.",
14032
+ ` ${others} already share memory on this machine. To let THIS repo see their accepted decisions:`,
14033
+ " 1. add the repo label(s) to `memory.share.subscribe` in massu.config.yaml",
14034
+ " 2. run `massu memory review` at your next session \u2014 you accept each decision explicitly.",
14035
+ " Nothing crosses without you accepting it, and nothing is ever an instruction. `massu memory share --help`."
14036
+ ].join("\n");
14037
+ }
14038
+ var crossRepoShareAdvisor = {
14039
+ id: CROSS_REPO_SHARE_ADVISOR_ID,
14040
+ remedyKeys: CROSS_REPO_SHARE_REMEDY_KEYS,
14041
+ isConfigured() {
14042
+ const share = getConfig().memory?.share;
14043
+ return Boolean(share?.enabled) || Array.isArray(share?.subscribe) && share.subscribe.length > 0;
14044
+ },
14045
+ async detect() {
14046
+ return detectShareableRepos(homedir9());
14047
+ }
14048
+ };
14049
+
13456
14050
  // src/hooks/session-start.ts
13457
14051
  init_consolidation_config();
13458
14052
 
13459
14053
  // src/lib/pidLiveness.ts
13460
- import { spawnSync } from "child_process";
14054
+ import { spawnSync as spawnSync2 } from "child_process";
13461
14055
  function isPidAlive(pid, opts = {}) {
13462
14056
  if (!Number.isFinite(pid) || pid <= 0) return false;
13463
14057
  const platform = opts.platformOverride ?? process.platform;
@@ -13482,7 +14076,7 @@ function checkPosix(pid, killOverride) {
13482
14076
  }
13483
14077
  function checkWindows(pid) {
13484
14078
  try {
13485
- const res = spawnSync("tasklist", ["/FI", `PID eq ${pid}`, "/NH"], {
14079
+ const res = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH"], {
13486
14080
  encoding: "utf-8",
13487
14081
  windowsHide: true
13488
14082
  });
@@ -13556,7 +14150,7 @@ Session memory, code intelligence, and governance are now active.
13556
14150
  }
13557
14151
  try {
13558
14152
  const consolidationCfg = resolveConsolidationConfig();
13559
- const advisorBlock = await runAdvisors([localModelAdvisor], {
14153
+ const advisorBlock = await runAdvisors([localModelAdvisor, crossRepoShareAdvisor], {
13560
14154
  enabled: consolidationCfg.enabled && consolidationCfg.suggestUpgrades,
13561
14155
  suggestIntervalDays: consolidationCfg.suggestIntervalDays
13562
14156
  });
@@ -13665,9 +14259,8 @@ async function buildContext(db, sessionId, source, tokenBudget, taskId) {
13665
14259
  }
13666
14260
  try {
13667
14261
  const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
13668
- if (existsSync21(knowledgeDbPath)) {
13669
- const Database3 = (await import("better-sqlite3")).default;
13670
- const kdb = new Database3(knowledgeDbPath, { readonly: true });
14262
+ if (existsSync24(knowledgeDbPath)) {
14263
+ const kdb = openDatabase2(knowledgeDbPath, { readonly: true, selfHeal: false });
13671
14264
  try {
13672
14265
  const stats = kdb.prepare(
13673
14266
  "SELECT COUNT(*) as doc_count, MAX(indexed_at) as last_indexed FROM knowledge_documents"
@@ -13729,8 +14322,8 @@ function estimateTokens(text) {
13729
14322
  }
13730
14323
  async function getGitBranch() {
13731
14324
  try {
13732
- const { spawnSync: spawnSync2 } = await import("child_process");
13733
- const result = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
14325
+ const { spawnSync: spawnSync3 } = await import("child_process");
14326
+ const result = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
13734
14327
  encoding: "utf-8",
13735
14328
  timeout: 5e3
13736
14329
  });
@@ -13756,8 +14349,8 @@ async function buildDriftBanner() {
13756
14349
  if (process.env.MASSU_DRIFT_QUIET === "1") return "";
13757
14350
  if (watcherIsLiveAndFresh()) return "";
13758
14351
  const configPath = resolve9(process.cwd(), "massu.config.yaml");
13759
- if (!existsSync21(configPath)) return "";
13760
- const content = readFileSync17(configPath, "utf-8");
14352
+ if (!existsSync24(configPath)) return "";
14353
+ const content = readFileSync19(configPath, "utf-8");
13761
14354
  const parsed = parseYaml4(content);
13762
14355
  if (!parsed || typeof parsed !== "object") return "";
13763
14356
  const det = parsed.detection;
@@ -13800,9 +14393,9 @@ function getMemoryDir() {
13800
14393
  }
13801
14394
  function loadCorrectionsPreventionRules() {
13802
14395
  try {
13803
- const correctionsPath = join15(getResolvedPaths().memoryDir, "corrections.md");
13804
- if (!existsSync21(correctionsPath)) return [];
13805
- return parseCorrectionRules(readFileSync17(correctionsPath, "utf-8"));
14396
+ const correctionsPath = join18(getResolvedPaths().memoryDir, "corrections.md");
14397
+ if (!existsSync24(correctionsPath)) return [];
14398
+ return parseCorrectionRules(readFileSync19(correctionsPath, "utf-8"));
13806
14399
  } catch (_e) {
13807
14400
  return [];
13808
14401
  }
@@ -13810,8 +14403,8 @@ function loadCorrectionsPreventionRules() {
13810
14403
  function readWatchStateRaw(cwd) {
13811
14404
  try {
13812
14405
  const path = resolve9(cwd, ".massu", "watch-state.json");
13813
- if (!existsSync21(path)) return null;
13814
- const obj = JSON.parse(readFileSync17(path, "utf-8"));
14406
+ if (!existsSync24(path)) return null;
14407
+ const obj = JSON.parse(readFileSync19(path, "utf-8"));
13815
14408
  if (!obj || typeof obj !== "object") return null;
13816
14409
  return obj;
13817
14410
  } catch {