@massu/core 1.16.3 → 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.
@@ -423,6 +423,136 @@ var init_sqlite_loader = __esm({
423
423
  }
424
424
  });
425
425
 
426
+ // src/db-driver.ts
427
+ import { createRequire as createRequire2 } from "module";
428
+ import { closeSync, existsSync as existsSync3, mkdtempSync, openSync, readFileSync as readFileSync3, rmSync as rmSync3, fsyncSync } from "fs";
429
+ import { tmpdir } from "os";
430
+ import { join as join2 } from "path";
431
+ function resolveDbEngine() {
432
+ return process.env[DB_ENGINE_ENV] === "better-sqlite3" ? "better-sqlite3" : DEFAULT_DB_ENGINE;
433
+ }
434
+ function nodeSqliteCtor() {
435
+ if (!_nodeCtor) _nodeCtor = req2("node:sqlite").DatabaseSync;
436
+ return _nodeCtor;
437
+ }
438
+ function plainRow(row) {
439
+ if (row == null || typeof row !== "object") return row;
440
+ return { ...row };
441
+ }
442
+ function openNodeSqlite(dbPath, opts) {
443
+ const Ctor = nodeSqliteCtor();
444
+ const raw = new Ctor(dbPath, {
445
+ open: true,
446
+ readOnly: !!opts.readonly,
447
+ // FAITHFULNESS: better-sqlite3's ACTUAL default is foreign_keys=ON (empirically
448
+ // verified — SQLite's own default is OFF, but bs3 enables it). massu was written +
449
+ // tested against that default, so the adapter must match it, or a store that relies
450
+ // on FK-ON-by-default (without an explicit pragma) would silently lose enforcement.
451
+ // Explicit `foreign_keys=OFF/ON` pragmas (e.g. memory-db bulk ops) still override.
452
+ enableForeignKeyConstraints: true,
453
+ allowExtension: false
454
+ });
455
+ let savepointSeq = 0;
456
+ const wrapStmt = (sql) => {
457
+ const st = raw.prepare(sql);
458
+ return {
459
+ run: (...p) => {
460
+ const r = st.run(...p);
461
+ return {
462
+ changes: typeof r.changes === "bigint" ? Number(r.changes) : r.changes,
463
+ lastInsertRowid: r.lastInsertRowid
464
+ };
465
+ },
466
+ get: (...p) => plainRow(st.get(...p)),
467
+ all: (...p) => st.all(...p).map(plainRow)
468
+ };
469
+ };
470
+ const pragma = (source, options) => {
471
+ const s = String(source).trim();
472
+ if (s.includes("=")) {
473
+ raw.exec(`PRAGMA ${s}`);
474
+ return void 0;
475
+ }
476
+ const rows = raw.prepare(`PRAGMA ${s}`).all().map(plainRow);
477
+ if (options?.simple) {
478
+ const first = rows[0];
479
+ return first ? Object.values(first)[0] : void 0;
480
+ }
481
+ return rows;
482
+ };
483
+ const transaction = (fn) => {
484
+ const run = (...args) => {
485
+ const nested = raw.isTransaction;
486
+ const name = `msp_${savepointSeq++}`;
487
+ if (nested) raw.exec(`SAVEPOINT ${name}`);
488
+ else raw.exec("BEGIN");
489
+ try {
490
+ const out = fn(...args);
491
+ if (nested) raw.exec(`RELEASE ${name}`);
492
+ else raw.exec("COMMIT");
493
+ return out;
494
+ } catch (e) {
495
+ if (nested) {
496
+ raw.exec(`ROLLBACK TO ${name}`);
497
+ raw.exec(`RELEASE ${name}`);
498
+ } else {
499
+ raw.exec("ROLLBACK");
500
+ }
501
+ throw e;
502
+ }
503
+ };
504
+ return run;
505
+ };
506
+ const serialize = () => {
507
+ if (raw.isTransaction) {
508
+ throw new Error(
509
+ "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."
510
+ );
511
+ }
512
+ const dir = mkdtempSync(join2(tmpdir(), "massu-serialize-"));
513
+ const out = join2(dir, "snapshot.db");
514
+ try {
515
+ raw.exec(`VACUUM INTO '${out.replace(/'/g, "''")}'`);
516
+ const fd = openSync(out, "r");
517
+ try {
518
+ fsyncSync(fd);
519
+ } finally {
520
+ closeSync(fd);
521
+ }
522
+ return readFileSync3(out);
523
+ } finally {
524
+ rmSync3(dir, { recursive: true, force: true });
525
+ }
526
+ };
527
+ const handle = {
528
+ prepare: (sql) => wrapStmt(sql),
529
+ exec: (sql) => raw.exec(sql),
530
+ pragma,
531
+ transaction,
532
+ serialize,
533
+ close: () => raw.close()
534
+ };
535
+ return handle;
536
+ }
537
+ function openBetterSqlite3(dbPath, opts) {
538
+ return openDatabase(dbPath, opts);
539
+ }
540
+ function openDatabase2(dbPath, opts = {}) {
541
+ return resolveDbEngine() === "better-sqlite3" ? openBetterSqlite3(dbPath, opts) : openNodeSqlite(dbPath, opts);
542
+ }
543
+ var req2, DEFAULT_DB_ENGINE, DB_ENGINE_ENV, _nodeCtor;
544
+ var init_db_driver = __esm({
545
+ "src/db-driver.ts"() {
546
+ "use strict";
547
+ init_sqlite_loader();
548
+ init_sqlite_loader();
549
+ req2 = createRequire2(import.meta.url);
550
+ DEFAULT_DB_ENGINE = "node-sqlite";
551
+ DB_ENGINE_ENV = "MASSU_DB_ENGINE";
552
+ _nodeCtor = null;
553
+ }
554
+ });
555
+
426
556
  // src/lib/memory-path.ts
427
557
  function encodeMemoryDirName(projectRoot) {
428
558
  return projectRoot.replace(/\//g, "-");
@@ -566,6 +696,32 @@ var init_config_memory_schema = __esm({
566
696
  // turn of EVERY session, so an unbounded index is a permanent context tax — and the
567
697
  // per-session cap bounds only the RATE, never the total.
568
698
  indexMaxLines: z.number().int().min(1).default(50)
699
+ }).default({}),
700
+ // --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
701
+ // A decision made in one of your repos can surface in another — opt-in per
702
+ // decision AND opt-in per repo, signed, verified, and materialized ONLY on
703
+ // explicit human acceptance. Local transport is FREE and zero-network.
704
+ //
705
+ // ⛔ BOTH switches default OFF, and OFF means NOTHING EXISTS — no registry, no
706
+ // keys, no inbox, no behavioural difference. Two INDEPENDENT opt-ins:
707
+ // • enabled — may this repo EXPORT its shareable decisions? (opt-in #1)
708
+ // • subscribe — which repo LABELS may this repo IMPORT from? (opt-in #2)
709
+ // `subscribe: []` means import NOTHING. There is deliberately NO `subscribe: all`
710
+ // — a repo you never named is a repo Massu never reads from.
711
+ share: z.object({
712
+ enabled: z.boolean().default(false),
713
+ subscribe: z.array(z.string()).default([]),
714
+ // C-04 — recall surfacing of cross-repo memories. `enabled` defaults true but is
715
+ // CONDITIONAL on `subscribe` being non-empty (empty by default), so the effective
716
+ // default is DORMANT: with `subscribe: []` the recall hook output is byte-identical
717
+ // to today's. `maxCrossRepoItems` caps how many accepted cross-repo items may appear
718
+ // per recall block (default 1); `minScore` is an OPTIONAL strictly-higher floor for
719
+ // cross-repo items (they are, by construction, less relevant than local ones).
720
+ recall: z.object({
721
+ enabled: z.boolean().default(true),
722
+ maxCrossRepoItems: z.number().int().min(0).default(1),
723
+ minScore: z.number().optional()
724
+ }).default({})
569
725
  }).default({})
570
726
  }).optional();
571
727
  }
@@ -573,7 +729,7 @@ var init_config_memory_schema = __esm({
573
729
 
574
730
  // src/config.ts
575
731
  import { resolve as resolve2, dirname as dirname3 } from "path";
576
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
732
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
577
733
  import { homedir as homedir2 } from "os";
578
734
  import { parse as parseYaml } from "yaml";
579
735
  import { z as z2 } from "zod";
@@ -581,7 +737,7 @@ function findProjectRoot() {
581
737
  const cwd = process.cwd();
582
738
  let dir = cwd;
583
739
  while (true) {
584
- if (existsSync3(resolve2(dir, "massu.config.yaml"))) {
740
+ if (existsSync4(resolve2(dir, "massu.config.yaml"))) {
585
741
  return dir;
586
742
  }
587
743
  const parent = dirname3(dir);
@@ -590,10 +746,10 @@ function findProjectRoot() {
590
746
  }
591
747
  dir = cwd;
592
748
  while (true) {
593
- if (existsSync3(resolve2(dir, "package.json"))) {
749
+ if (existsSync4(resolve2(dir, "package.json"))) {
594
750
  return dir;
595
751
  }
596
- if (existsSync3(resolve2(dir, ".git"))) {
752
+ if (existsSync4(resolve2(dir, ".git"))) {
597
753
  return dir;
598
754
  }
599
755
  const parent = dirname3(dir);
@@ -613,8 +769,8 @@ function getConfig() {
613
769
  const root = getProjectRoot();
614
770
  const configPath = resolve2(root, "massu.config.yaml");
615
771
  let rawYaml = {};
616
- if (existsSync3(configPath)) {
617
- const content = readFileSync3(configPath, "utf-8");
772
+ if (existsSync4(configPath)) {
773
+ const content = readFileSync4(configPath, "utf-8");
618
774
  rawYaml = parseYaml(content) ?? {};
619
775
  }
620
776
  const result = RawConfigSchema.safeParse(rawYaml);
@@ -819,7 +975,6 @@ var init_config = __esm({
819
975
  custom_patterns: z2.array(CustomPatternSchema).default([])
820
976
  }).optional(),
821
977
  adr: z2.object({
822
- detection_phrases: z2.array(z2.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
823
978
  template: z2.string().default("default"),
824
979
  storage: z2.string().default("database"),
825
980
  output_dir: z2.string().default("docs/adr")
@@ -1173,26 +1328,26 @@ var init_memory_vector = __esm({
1173
1328
  });
1174
1329
 
1175
1330
  // src/db-backup.ts
1176
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync3 } from "fs";
1177
- import { resolve as resolve3, join as join2, basename, dirname as dirname4 } from "path";
1331
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1332
+ import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
1178
1333
  import { homedir as homedir3 } from "os";
1179
1334
  function dbBackupsRoot(home = homedir3()) {
1180
1335
  return resolve3(home, ".massu", "db-backups");
1181
1336
  }
1182
1337
  function projectBackupDir(projectRoot, home = homedir3()) {
1183
1338
  const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
1184
- return join2(dbBackupsRoot(home), slug);
1339
+ return join3(dbBackupsRoot(home), slug);
1185
1340
  }
1186
1341
  function backupStamp(nowMs) {
1187
1342
  return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
1188
1343
  }
1189
1344
  function listDbBackups(projectRoot, home = homedir3()) {
1190
1345
  const dir = projectBackupDir(projectRoot, home);
1191
- if (!existsSync4(dir)) return [];
1346
+ if (!existsSync5(dir)) return [];
1192
1347
  const out = [];
1193
1348
  for (const f of readdirSync(dir)) {
1194
1349
  if (!f.endsWith(".db")) continue;
1195
- const p = join2(dir, f);
1350
+ const p = join3(dir, f);
1196
1351
  const st = statSync(p);
1197
1352
  const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
1198
1353
  out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
@@ -1205,13 +1360,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
1205
1360
  if (backups.length === 0) return false;
1206
1361
  const newest = backups[0];
1207
1362
  if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
1208
- if (existsSync4(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
1363
+ if (existsSync5(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
1209
1364
  return true;
1210
1365
  }
1211
1366
  function integrityOk(dbPath) {
1212
1367
  let db = null;
1213
1368
  try {
1214
- db = openDatabase(dbPath, { readonly: true });
1369
+ db = openDatabase2(dbPath, { readonly: true });
1215
1370
  const rows = db.pragma("integrity_check");
1216
1371
  return rows.length === 1 && rows[0].integrity_check === "ok";
1217
1372
  } catch {
@@ -1224,16 +1379,16 @@ function integrityOk(dbPath) {
1224
1379
  }
1225
1380
  }
1226
1381
  function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
1227
- if (!existsSync4(dbPath)) {
1382
+ if (!existsSync5(dbPath)) {
1228
1383
  throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
1229
1384
  }
1230
1385
  const dir = projectBackupDir(projectRoot, home);
1231
1386
  mkdirSync4(dir, { recursive: true });
1232
1387
  const name = basename(dbPath, ".db");
1233
- const dest = join2(dir, `${name}-${backupStamp(nowMs)}.db`);
1388
+ const dest = join3(dir, `${name}-${backupStamp(nowMs)}.db`);
1234
1389
  let src = null;
1235
1390
  try {
1236
- src = openDatabase(dbPath, { readonly: true });
1391
+ src = openDatabase2(dbPath, { readonly: true });
1237
1392
  src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
1238
1393
  } catch (err) {
1239
1394
  throw new DbBackupError(
@@ -1272,7 +1427,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
1272
1427
  }
1273
1428
  function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
1274
1429
  try {
1275
- if (!existsSync4(dbPath)) return null;
1430
+ if (!existsSync5(dbPath)) return null;
1276
1431
  if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
1277
1432
  return backupDb(projectRoot, dbPath, nowMs, home);
1278
1433
  } catch (err) {
@@ -1284,7 +1439,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
1284
1439
  var init_db_backup = __esm({
1285
1440
  "src/db-backup.ts"() {
1286
1441
  "use strict";
1287
- init_sqlite_loader();
1442
+ init_db_driver();
1288
1443
  DEFAULT_RETENTION = 5;
1289
1444
  FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
1290
1445
  DbBackupError = class extends Error {
@@ -1372,8 +1527,8 @@ var init_rule_delivery = __esm({
1372
1527
  });
1373
1528
 
1374
1529
  // src/rule-candidate-store.ts
1375
- import { existsSync as existsSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "node:fs";
1376
- import { join as join3 } from "node:path";
1530
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
1531
+ import { join as join4 } from "node:path";
1377
1532
  function ensureRuleCandidatesTable(db) {
1378
1533
  db.exec(`
1379
1534
  CREATE TABLE IF NOT EXISTS rule_candidates (
@@ -1393,7 +1548,7 @@ function ensureRuleCandidatesTable(db) {
1393
1548
  `);
1394
1549
  }
1395
1550
  function sidecarPath(projectRoot, promptHash) {
1396
- return join3(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
1551
+ return join4(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
1397
1552
  }
1398
1553
  function upsertCandidate(db, projectRoot, candidate, opts = {}) {
1399
1554
  ensureRuleCandidatesTable(db);
@@ -1408,10 +1563,10 @@ function upsertCandidate(db, projectRoot, candidate, opts = {}) {
1408
1563
  origin = excluded.origin,
1409
1564
  updated_at = datetime('now')
1410
1565
  `).run(candidate.prompt_hash, origin, score, JSON.stringify(candidate));
1411
- const dir = join3(projectRoot, ".massu", "rule-candidates");
1566
+ const dir = join4(projectRoot, ".massu", "rule-candidates");
1412
1567
  mkdirSync5(dir, { recursive: true });
1413
1568
  const path = sidecarPath(projectRoot, candidate.prompt_hash);
1414
- if (!existsSync5(path)) {
1569
+ if (!existsSync6(path)) {
1415
1570
  writeFileSync3(path, JSON.stringify(candidate, null, 2), "utf-8");
1416
1571
  }
1417
1572
  }
@@ -1422,21 +1577,21 @@ var init_rule_candidate_store = __esm({
1422
1577
  });
1423
1578
 
1424
1579
  // src/hooks/lib/hook-failure-signal.ts
1425
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6, existsSync as existsSync6 } from "fs";
1426
- import { join as join4, dirname as dirname5 } from "path";
1580
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6, existsSync as existsSync7 } from "fs";
1581
+ import { join as join5, dirname as dirname5 } from "path";
1427
1582
  function resolveFailureLogPath() {
1428
1583
  const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
1429
1584
  if (explicit) return explicit;
1430
1585
  let dir = process.cwd();
1431
1586
  for (let i = 0; i < 12; i++) {
1432
- if (existsSync6(join4(dir, ".massu")) || existsSync6(join4(dir, "massu.config.yaml"))) {
1433
- return join4(dir, ".massu", "hook-failures.jsonl");
1587
+ if (existsSync7(join5(dir, ".massu")) || existsSync7(join5(dir, "massu.config.yaml"))) {
1588
+ return join5(dir, ".massu", "hook-failures.jsonl");
1434
1589
  }
1435
1590
  const parent = dirname5(dir);
1436
1591
  if (parent === dir) break;
1437
1592
  dir = parent;
1438
1593
  }
1439
- return join4(process.cwd(), ".massu", "hook-failures.jsonl");
1594
+ return join5(process.cwd(), ".massu", "hook-failures.jsonl");
1440
1595
  }
1441
1596
  function recordHookFailure(hook, error, context) {
1442
1597
  const record = {
@@ -1493,9 +1648,9 @@ var init_hook_failure_signal = __esm({
1493
1648
  });
1494
1649
 
1495
1650
  // src/memory-embedder-tokenizer.ts
1496
- import { readFileSync as readFileSync5 } from "fs";
1651
+ import { readFileSync as readFileSync6 } from "fs";
1497
1652
  function loadVocab(vocabPath) {
1498
- const lines = readFileSync5(vocabPath, "utf-8").split("\n");
1653
+ const lines = readFileSync6(vocabPath, "utf-8").split("\n");
1499
1654
  const vocab = /* @__PURE__ */ new Map();
1500
1655
  for (let i = 0; i < lines.length; i++) {
1501
1656
  const tok = lines[i].replace(/\r$/, "");
@@ -1615,9 +1770,9 @@ var init_memory_embedder_tokenizer = __esm({
1615
1770
 
1616
1771
  // src/memory-embedder.ts
1617
1772
  import { fileURLToPath } from "url";
1618
- import { dirname as dirname6, join as join5 } from "path";
1619
- import { existsSync as existsSync7 } from "fs";
1620
- import { createRequire as createRequire2 } from "module";
1773
+ import { dirname as dirname6, join as join6 } from "path";
1774
+ import { existsSync as existsSync8 } from "fs";
1775
+ import { createRequire as createRequire3 } from "module";
1621
1776
  function getActiveEmbedModel() {
1622
1777
  return _activeModel;
1623
1778
  }
@@ -1641,7 +1796,7 @@ function resolveModelDir() {
1641
1796
  let dir = dirname6(fileURLToPath(import.meta.url));
1642
1797
  let root = null;
1643
1798
  for (let i = 0; i < 8; i++) {
1644
- if (existsSync7(join5(dir, "package.json"))) {
1799
+ if (existsSync8(join6(dir, "package.json"))) {
1645
1800
  root = dir;
1646
1801
  break;
1647
1802
  }
@@ -1651,8 +1806,8 @@ function resolveModelDir() {
1651
1806
  }
1652
1807
  if (!root) return null;
1653
1808
  for (const rel of ["dist/embedder", "assets/embedder"]) {
1654
- const candidate = join5(root, rel);
1655
- if (existsSync7(join5(candidate, "model_quantized.onnx"))) return candidate;
1809
+ const candidate = join6(root, rel);
1810
+ if (existsSync8(join6(candidate, "model_quantized.onnx"))) return candidate;
1656
1811
  }
1657
1812
  return null;
1658
1813
  } catch {
@@ -1661,8 +1816,8 @@ function resolveModelDir() {
1661
1816
  }
1662
1817
  function resolveWasmDir() {
1663
1818
  try {
1664
- const req2 = createRequire2(import.meta.url);
1665
- const main2 = req2.resolve(ORT_PKG);
1819
+ const req3 = createRequire3(import.meta.url);
1820
+ const main2 = req3.resolve(ORT_PKG);
1666
1821
  return dirname6(main2);
1667
1822
  } catch {
1668
1823
  return null;
@@ -1683,8 +1838,8 @@ async function getSession() {
1683
1838
  ort.env.wasm.proxy = false;
1684
1839
  ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
1685
1840
  _ortTensor = ort.Tensor;
1686
- const modelPath = join5(modelDir, "model_quantized.onnx");
1687
- const vocabPath = join5(modelDir, "vocab.txt");
1841
+ const modelPath = join6(modelDir, "model_quantized.onnx");
1842
+ const vocabPath = join6(modelDir, "vocab.txt");
1688
1843
  _vocab = loadVocab(vocabPath);
1689
1844
  const session = await ort.InferenceSession.create(modelPath, {
1690
1845
  executionProviders: ["wasm"],
@@ -2005,6 +2160,7 @@ __export(memory_db_exports, {
2005
2160
  migrateAuditLogCheckExtension: () => migrateAuditLogCheckExtension,
2006
2161
  migrateMemoryFilesFor4B: () => migrateMemoryFilesFor4B,
2007
2162
  migrateObservationEmbeddingChunks: () => migrateObservationEmbeddingChunks,
2163
+ migrateSharedMemoryFor5B: () => migrateSharedMemoryFor5B,
2008
2164
  pruneOldConversationTurns: () => pruneOldConversationTurns,
2009
2165
  pruneOldObservations: () => pruneOldObservations,
2010
2166
  pruneToolCostEvents: () => pruneToolCostEvents,
@@ -2022,7 +2178,7 @@ __export(memory_db_exports, {
2022
2178
  usageWarmupElapsed: () => usageWarmupElapsed
2023
2179
  });
2024
2180
  import { dirname as dirname7, basename as basename2 } from "path";
2025
- import { existsSync as existsSync8, mkdirSync as mkdirSync7 } from "fs";
2181
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7 } from "fs";
2026
2182
  function sanitizeFts5Query(raw) {
2027
2183
  const trimmed = raw.trim();
2028
2184
  if (!trimmed) return '""';
@@ -2039,11 +2195,11 @@ function sanitizeFts5QueryOr(raw) {
2039
2195
  function getMemoryDb() {
2040
2196
  const dbPath = getResolvedPaths().memoryDbPath;
2041
2197
  const dir = dirname7(dbPath);
2042
- if (!existsSync8(dir)) {
2198
+ if (!existsSync9(dir)) {
2043
2199
  mkdirSync7(dir, { recursive: true });
2044
2200
  }
2045
- const preExisting = existsSync8(dbPath);
2046
- const db = openDatabase(dbPath);
2201
+ const preExisting = existsSync9(dbPath);
2202
+ const db = openDatabase2(dbPath);
2047
2203
  db.pragma("journal_mode = WAL");
2048
2204
  db.pragma("foreign_keys = ON");
2049
2205
  const onDisk = db.pragma("user_version", { simple: true });
@@ -2119,6 +2275,39 @@ function migrateMemoryFilesFor4B(db) {
2119
2275
  db.exec(`ALTER TABLE ${add.table} ADD COLUMN ${add.name} ${add.decl}`);
2120
2276
  }
2121
2277
  }
2278
+ function migrateSharedMemoryFor5B(db) {
2279
+ const cols = db.prepare(`PRAGMA table_info(observations)`).all();
2280
+ if (cols.length > 0 && !cols.some((c) => c.name === "shareable")) {
2281
+ db.exec(`ALTER TABLE observations ADD COLUMN shareable INTEGER NOT NULL DEFAULT 0`);
2282
+ }
2283
+ db.exec(`
2284
+ CREATE TABLE IF NOT EXISTS shared_memory_pending (
2285
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2286
+ record_hash TEXT NOT NULL UNIQUE, -- identity + idempotency key (hex sha256)
2287
+ origin_repo_id TEXT NOT NULL, -- the signed origin repo_id (v4 UUID)
2288
+ origin_repo_label TEXT NOT NULL, -- re-slugged on read; NEVER trusted from the wire
2289
+ envelope_raw TEXT NOT NULL, -- the VERBATIM signed bytes; B-05 re-verifies these
2290
+ record_json TEXT NOT NULL, -- the single record's canonical JSON
2291
+ received_at_epoch INTEGER NOT NULL, -- epoch SECONDS (Slice-2 convention)
2292
+ accepted_at_epoch INTEGER, -- set on accept (B-05); NULL while pending
2293
+ refused_at_epoch INTEGER, -- set on refuse (B-06)
2294
+ expired_at_epoch INTEGER -- revocation \u21D2 EXPIRE, never DELETE (B-07 / S-3)
2295
+ );
2296
+ CREATE INDEX IF NOT EXISTS idx_smp_origin ON shared_memory_pending(origin_repo_id);
2297
+ CREATE INDEX IF NOT EXISTS idx_smp_received ON shared_memory_pending(received_at_epoch DESC);
2298
+ `);
2299
+ db.exec(`
2300
+ CREATE TABLE IF NOT EXISTS shared_memory_outbound (
2301
+ record_hash TEXT PRIMARY KEY, -- the exported record's canonical hash
2302
+ observation_id INTEGER NOT NULL, -- the source observations.id (for B-07 revocation)
2303
+ origin_repo_id TEXT NOT NULL, -- this repo's own repo_id at export time
2304
+ seq INTEGER NOT NULL, -- the envelope seq this record first went out in
2305
+ exported_at_epoch INTEGER NOT NULL, -- epoch SECONDS
2306
+ revoked_at_epoch INTEGER -- set when the source row is superseded/expired (B-07)
2307
+ );
2308
+ CREATE INDEX IF NOT EXISTS idx_smo_obs ON shared_memory_outbound(observation_id);
2309
+ `);
2310
+ }
2122
2311
  function migrateAuditLogCheckExtension(db) {
2123
2312
  const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
2124
2313
  if (!row) return;
@@ -2141,7 +2330,16 @@ function migrateAuditLogCheckExtension(db) {
2141
2330
  "memory_file_adopted_human",
2142
2331
  "memory_file_rendered",
2143
2332
  "memory_file_render_refused",
2144
- "memory_file_tombstoned"
2333
+ "memory_file_tombstoned",
2334
+ // B-10 (Slice 5) — the seven cross-repo shared-memory events. An existing DB whose
2335
+ // CHECK lacks any of these triggers the table rebuild below.
2336
+ "shared_memory_exported",
2337
+ "shared_memory_export_refused",
2338
+ "shared_memory_imported",
2339
+ "shared_memory_dropped",
2340
+ "shared_memory_accepted",
2341
+ "shared_memory_refused",
2342
+ "shared_memory_revoked"
2145
2343
  ];
2146
2344
  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);
2147
2345
  if (checkClauseMatch) {
@@ -2160,7 +2358,12 @@ function migrateAuditLogCheckExtension(db) {
2160
2358
  'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
2161
2359
  'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
2162
2360
  'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
2163
- 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
2361
+ 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
2362
+ -- B-10 (Slice 5) \u2014 cross-repo shared-memory events (must mirror the CREATE +
2363
+ -- the expected[] list above, or the migration would loop rebuilding forever).
2364
+ 'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
2365
+ 'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
2366
+ 'shared_memory_revoked'
2164
2367
  )),
2165
2368
  actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
2166
2369
  model_id TEXT,
@@ -2237,6 +2440,12 @@ function initMemorySchema(db) {
2237
2440
  -- credential, or takes a snapshot. A Slice-5 synced memory arrives here with
2238
2441
  -- origin='team' and must never reach disk without CR-55's gates.
2239
2442
  origin TEXT NOT NULL DEFAULT 'local',
2443
+ -- B-01 (Slice 5, cross-repo surfacing): the per-decision SHARE opt-in. Set to 1
2444
+ -- ONLY by an explicit human act (massu memory share <id>); NEVER by a heuristic,
2445
+ -- score, LLM, hook, or consolidation pass. Export (B-02) reads WHERE shareable=1
2446
+ -- AND origin='local'. Defaults 0 so every pre-existing and machine-written row is
2447
+ -- un-shareable until a human says otherwise (fail-closed).
2448
+ shareable INTEGER NOT NULL DEFAULT 0,
2240
2449
  created_at TEXT NOT NULL,
2241
2450
  created_at_epoch INTEGER NOT NULL,
2242
2451
  FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
@@ -2510,7 +2719,13 @@ function initMemorySchema(db) {
2510
2719
  'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
2511
2720
  'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
2512
2721
  'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
2513
- 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
2722
+ 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
2723
+ -- B-10 (Slice 5): cross-repo shared-memory observability. Without these seven,
2724
+ -- nobody could ever answer "what has crossed between my repos?" (and an ingest
2725
+ -- catch would swallow the CHECK violation, silently producing NOTHING).
2726
+ 'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
2727
+ 'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
2728
+ 'shared_memory_revoked'
2514
2729
  )),
2515
2730
  actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
2516
2731
  model_id TEXT,
@@ -2879,6 +3094,7 @@ function initMemorySchema(db) {
2879
3094
  `);
2880
3095
  migrateObservationEmbeddingChunks(db);
2881
3096
  migrateMemoryFilesFor4B(db);
3097
+ migrateSharedMemoryFor5B(db);
2882
3098
  ensureRuleCandidatesTable(db);
2883
3099
  migrateRuleDelivery(db);
2884
3100
  }
@@ -3749,7 +3965,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
3749
3965
  var init_memory_db = __esm({
3750
3966
  "src/memory-db.ts"() {
3751
3967
  "use strict";
3752
- init_sqlite_loader();
3968
+ init_db_driver();
3753
3969
  init_config();
3754
3970
  init_memory_vector();
3755
3971
  init_db_backup();
@@ -3771,10 +3987,10 @@ var init_memory_db = __esm({
3771
3987
 
3772
3988
  // src/hooks/user-prompt.ts
3773
3989
  init_memory_db();
3774
- init_sqlite_loader();
3990
+ init_db_driver();
3775
3991
  init_config();
3776
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, writeFileSync as writeFileSync4, readFileSync as readFileSync6, readdirSync as readdirSync3, openSync, fstatSync, readSync, closeSync } from "fs";
3777
- import { join as join6 } from "path";
3992
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync4, readFileSync as readFileSync7, readdirSync as readdirSync3, openSync as openSync2, fstatSync, readSync, closeSync as closeSync2 } from "fs";
3993
+ import { join as join7 } from "path";
3778
3994
 
3779
3995
  // src/rule-candidate-detector.ts
3780
3996
  var CORRECTION_DISMISSAL_PATTERN = /\b(nevermind|never\s+mind|forget\s+it|ignore\s+(that|it)|actually\s+you('?re|\s+were)\s+right|on\s+second\s+thought|no\s+wait|scratch\s+that|disregard|abandon\s+(that|it))\b/i;
@@ -4115,8 +4331,8 @@ async function main() {
4115
4331
  const fileRefs = extractFileReferences(prompt);
4116
4332
  if (fileRefs.length > 0) {
4117
4333
  const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
4118
- if (knowledgeDbPath && existsSync9(knowledgeDbPath)) {
4119
- const kdb = openDatabase(knowledgeDbPath, { readonly: true, selfHeal: false });
4334
+ if (knowledgeDbPath && existsSync10(knowledgeDbPath)) {
4335
+ const kdb = openDatabase2(knowledgeDbPath, { readonly: true, selfHeal: false });
4120
4336
  try {
4121
4337
  const placeholders = fileRefs.map(() => "?").join(",");
4122
4338
  const matches = kdb.prepare(
@@ -4168,14 +4384,14 @@ async function main() {
4168
4384
  category: categorizePrompt(prompt),
4169
4385
  blacklist
4170
4386
  });
4171
- const candidateDir = join6(hookInput.cwd, ".massu", "rule-candidates");
4387
+ const candidateDir = join7(hookInput.cwd, ".massu", "rule-candidates");
4172
4388
  if (scoreResult.emitCandidate) {
4173
4389
  const cachedTier = getCachedTierReadOnly(db);
4174
4390
  if (entitledForAutoLearning(cachedTier)) {
4175
4391
  mkdirSync8(candidateDir, { recursive: true });
4176
4392
  const promptHash = hashPrompt(prompt);
4177
- const candidatePath = join6(candidateDir, `${promptHash}.json`);
4178
- if (!existsSync9(candidatePath)) {
4393
+ const candidatePath = join7(candidateDir, `${promptHash}.json`);
4394
+ if (!existsSync10(candidatePath)) {
4179
4395
  upsertCandidate(db, hookInput.cwd, {
4180
4396
  prompt,
4181
4397
  prompt_hash: promptHash,
@@ -4199,8 +4415,8 @@ async function main() {
4199
4415
  }
4200
4416
  }
4201
4417
  } else {
4202
- const nudgePath = join6(candidateDir, ".last-tier-nudge");
4203
- if (!existsSync9(nudgePath)) {
4418
+ const nudgePath = join7(candidateDir, ".last-tier-nudge");
4419
+ if (!existsSync10(nudgePath)) {
4204
4420
  mkdirSync8(candidateDir, { recursive: true });
4205
4421
  process.stderr.write(
4206
4422
  `
@@ -4212,15 +4428,15 @@ async function main() {
4212
4428
  }
4213
4429
  }
4214
4430
  }
4215
- if (existsSync9(candidateDir)) {
4431
+ if (existsSync10(candidateDir)) {
4216
4432
  const candidates = readdirSync3(candidateDir).filter(
4217
4433
  (f) => f.endsWith(".json") && !f.startsWith(".")
4218
4434
  );
4219
4435
  const candidateCount = candidates.length;
4220
- const surfacedPath = join6(candidateDir, ".last-surfaced");
4436
+ const surfacedPath = join7(candidateDir, ".last-surfaced");
4221
4437
  let lastSurfaced = 0;
4222
- if (existsSync9(surfacedPath)) {
4223
- const raw = readFileSync6(surfacedPath, "utf-8").trim();
4438
+ if (existsSync10(surfacedPath)) {
4439
+ const raw = readFileSync7(surfacedPath, "utf-8").trim();
4224
4440
  const parsed = parseInt(raw, 10);
4225
4441
  if (!Number.isNaN(parsed)) lastSurfaced = parsed;
4226
4442
  }
@@ -4236,10 +4452,10 @@ async function main() {
4236
4452
  }
4237
4453
  } catch (candidateErr) {
4238
4454
  try {
4239
- const dir = join6(hookInput.cwd, ".massu", "rule-candidates");
4240
- if (!existsSync9(dir)) mkdirSync8(dir, { recursive: true });
4241
- const logPath = join6(dir, ".detector-failures.jsonl");
4242
- const pre = existsSync9(logPath) ? readFileSync6(logPath, "utf-8") : "";
4455
+ const dir = join7(hookInput.cwd, ".massu", "rule-candidates");
4456
+ if (!existsSync10(dir)) mkdirSync8(dir, { recursive: true });
4457
+ const logPath = join7(dir, ".detector-failures.jsonl");
4458
+ const pre = existsSync10(logPath) ? readFileSync7(logPath, "utf-8") : "";
4243
4459
  const sep = pre && !pre.endsWith("\n") ? "\n" : "";
4244
4460
  writeFileSync4(logPath, pre + sep + JSON.stringify({
4245
4461
  session_id,
@@ -4262,13 +4478,13 @@ async function main() {
4262
4478
  }
4263
4479
  function detectPriorAssistantTurn(transcriptPath) {
4264
4480
  try {
4265
- if (!transcriptPath || !existsSync9(transcriptPath)) {
4481
+ if (!transcriptPath || !existsSync10(transcriptPath)) {
4266
4482
  return { hadEditOrWrite: false, files: [] };
4267
4483
  }
4268
4484
  if (!transcriptPath.includes("/.claude/projects/")) {
4269
4485
  return { hadEditOrWrite: false, files: [] };
4270
4486
  }
4271
- const fd = openSync(transcriptPath, "r");
4487
+ const fd = openSync2(transcriptPath, "r");
4272
4488
  let buf;
4273
4489
  try {
4274
4490
  const stats = fstatSync(fd);
@@ -4277,7 +4493,7 @@ function detectPriorAssistantTurn(transcriptPath) {
4277
4493
  buf = Buffer.alloc(readLen);
4278
4494
  readSync(fd, buf, 0, readLen, offset);
4279
4495
  } finally {
4280
- closeSync(fd);
4496
+ closeSync2(fd);
4281
4497
  }
4282
4498
  const lines = buf.toString("utf-8").split("\n").filter(Boolean);
4283
4499
  const files = [];