@massu/core 1.16.3 → 2.1.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.
@@ -446,6 +446,136 @@ var init_sqlite_loader = __esm({
446
446
  }
447
447
  });
448
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
+
449
579
  // src/lib/memory-path.ts
450
580
  function encodeMemoryDirName(projectRoot) {
451
581
  return projectRoot.replace(/\//g, "-");
@@ -589,6 +719,32 @@ var init_config_memory_schema = __esm({
589
719
  // turn of EVERY session, so an unbounded index is a permanent context tax — and the
590
720
  // per-session cap bounds only the RATE, never the total.
591
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({})
592
748
  }).default({})
593
749
  }).optional();
594
750
  }
@@ -596,7 +752,7 @@ var init_config_memory_schema = __esm({
596
752
 
597
753
  // src/config.ts
598
754
  import { resolve as resolve2, dirname as dirname3 } from "path";
599
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
755
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
600
756
  import { homedir as homedir2 } from "os";
601
757
  import { parse as parseYaml } from "yaml";
602
758
  import { z as z2 } from "zod";
@@ -604,7 +760,7 @@ function findProjectRoot() {
604
760
  const cwd = process.cwd();
605
761
  let dir = cwd;
606
762
  while (true) {
607
- if (existsSync3(resolve2(dir, "massu.config.yaml"))) {
763
+ if (existsSync4(resolve2(dir, "massu.config.yaml"))) {
608
764
  return dir;
609
765
  }
610
766
  const parent = dirname3(dir);
@@ -613,10 +769,10 @@ function findProjectRoot() {
613
769
  }
614
770
  dir = cwd;
615
771
  while (true) {
616
- if (existsSync3(resolve2(dir, "package.json"))) {
772
+ if (existsSync4(resolve2(dir, "package.json"))) {
617
773
  return dir;
618
774
  }
619
- if (existsSync3(resolve2(dir, ".git"))) {
775
+ if (existsSync4(resolve2(dir, ".git"))) {
620
776
  return dir;
621
777
  }
622
778
  const parent = dirname3(dir);
@@ -636,8 +792,8 @@ function getConfig() {
636
792
  const root = getProjectRoot();
637
793
  const configPath = resolve2(root, "massu.config.yaml");
638
794
  let rawYaml = {};
639
- if (existsSync3(configPath)) {
640
- const content = readFileSync3(configPath, "utf-8");
795
+ if (existsSync4(configPath)) {
796
+ const content = readFileSync4(configPath, "utf-8");
641
797
  rawYaml = parseYaml(content) ?? {};
642
798
  }
643
799
  const result = RawConfigSchema.safeParse(rawYaml);
@@ -765,6 +921,7 @@ function getResolvedPaths() {
765
921
  sessionStatePath: resolve2(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
766
922
  sessionArchivePath: resolve2(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
767
923
  mcpJsonPath: resolve2(root, ".mcp.json"),
924
+ settingsPath: resolve2(root, claudeDirName, "settings.json"),
768
925
  settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
769
926
  };
770
927
  }
@@ -842,7 +999,6 @@ var init_config = __esm({
842
999
  custom_patterns: z2.array(CustomPatternSchema).default([])
843
1000
  }).optional(),
844
1001
  adr: z2.object({
845
- detection_phrases: z2.array(z2.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
846
1002
  template: z2.string().default("default"),
847
1003
  storage: z2.string().default("database"),
848
1004
  output_dir: z2.string().default("docs/adr")
@@ -1196,26 +1352,26 @@ var init_memory_vector = __esm({
1196
1352
  });
1197
1353
 
1198
1354
  // src/db-backup.ts
1199
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync3 } from "fs";
1200
- import { resolve as resolve3, join as join2, basename, dirname as dirname4 } from "path";
1355
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1356
+ import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
1201
1357
  import { homedir as homedir3 } from "os";
1202
1358
  function dbBackupsRoot(home = homedir3()) {
1203
1359
  return resolve3(home, ".massu", "db-backups");
1204
1360
  }
1205
1361
  function projectBackupDir(projectRoot, home = homedir3()) {
1206
1362
  const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
1207
- return join2(dbBackupsRoot(home), slug);
1363
+ return join3(dbBackupsRoot(home), slug);
1208
1364
  }
1209
1365
  function backupStamp(nowMs) {
1210
1366
  return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
1211
1367
  }
1212
1368
  function listDbBackups(projectRoot, home = homedir3()) {
1213
1369
  const dir = projectBackupDir(projectRoot, home);
1214
- if (!existsSync4(dir)) return [];
1370
+ if (!existsSync5(dir)) return [];
1215
1371
  const out = [];
1216
1372
  for (const f of readdirSync(dir)) {
1217
1373
  if (!f.endsWith(".db")) continue;
1218
- const p = join2(dir, f);
1374
+ const p = join3(dir, f);
1219
1375
  const st = statSync(p);
1220
1376
  const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
1221
1377
  out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
@@ -1228,13 +1384,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
1228
1384
  if (backups.length === 0) return false;
1229
1385
  const newest = backups[0];
1230
1386
  if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
1231
- if (existsSync4(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
1387
+ if (existsSync5(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
1232
1388
  return true;
1233
1389
  }
1234
1390
  function integrityOk(dbPath) {
1235
1391
  let db = null;
1236
1392
  try {
1237
- db = openDatabase(dbPath, { readonly: true });
1393
+ db = openDatabase2(dbPath, { readonly: true });
1238
1394
  const rows = db.pragma("integrity_check");
1239
1395
  return rows.length === 1 && rows[0].integrity_check === "ok";
1240
1396
  } catch {
@@ -1247,16 +1403,16 @@ function integrityOk(dbPath) {
1247
1403
  }
1248
1404
  }
1249
1405
  function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
1250
- if (!existsSync4(dbPath)) {
1406
+ if (!existsSync5(dbPath)) {
1251
1407
  throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
1252
1408
  }
1253
1409
  const dir = projectBackupDir(projectRoot, home);
1254
1410
  mkdirSync4(dir, { recursive: true });
1255
1411
  const name = basename(dbPath, ".db");
1256
- const dest = join2(dir, `${name}-${backupStamp(nowMs)}.db`);
1412
+ const dest = join3(dir, `${name}-${backupStamp(nowMs)}.db`);
1257
1413
  let src = null;
1258
1414
  try {
1259
- src = openDatabase(dbPath, { readonly: true });
1415
+ src = openDatabase2(dbPath, { readonly: true });
1260
1416
  src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
1261
1417
  } catch (err) {
1262
1418
  throw new DbBackupError(
@@ -1295,7 +1451,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
1295
1451
  }
1296
1452
  function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
1297
1453
  try {
1298
- if (!existsSync4(dbPath)) return null;
1454
+ if (!existsSync5(dbPath)) return null;
1299
1455
  if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
1300
1456
  return backupDb(projectRoot, dbPath, nowMs, home);
1301
1457
  } catch (err) {
@@ -1307,7 +1463,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
1307
1463
  var init_db_backup = __esm({
1308
1464
  "src/db-backup.ts"() {
1309
1465
  "use strict";
1310
- init_sqlite_loader();
1466
+ init_db_driver();
1311
1467
  DEFAULT_RETENTION = 5;
1312
1468
  FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
1313
1469
  DbBackupError = class extends Error {
@@ -1420,21 +1576,21 @@ var init_rule_candidate_store = __esm({
1420
1576
  });
1421
1577
 
1422
1578
  // src/hooks/lib/hook-failure-signal.ts
1423
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync5 } from "fs";
1424
- import { join as join3, dirname as dirname5 } from "path";
1579
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
1580
+ import { join as join4, dirname as dirname5 } from "path";
1425
1581
  function resolveFailureLogPath() {
1426
1582
  const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
1427
1583
  if (explicit) return explicit;
1428
1584
  let dir = process.cwd();
1429
1585
  for (let i = 0; i < 12; i++) {
1430
- if (existsSync5(join3(dir, ".massu")) || existsSync5(join3(dir, "massu.config.yaml"))) {
1431
- return join3(dir, ".massu", "hook-failures.jsonl");
1586
+ if (existsSync6(join4(dir, ".massu")) || existsSync6(join4(dir, "massu.config.yaml"))) {
1587
+ return join4(dir, ".massu", "hook-failures.jsonl");
1432
1588
  }
1433
1589
  const parent = dirname5(dir);
1434
1590
  if (parent === dir) break;
1435
1591
  dir = parent;
1436
1592
  }
1437
- return join3(process.cwd(), ".massu", "hook-failures.jsonl");
1593
+ return join4(process.cwd(), ".massu", "hook-failures.jsonl");
1438
1594
  }
1439
1595
  function recordHookFailure(hook, error, context) {
1440
1596
  const record = {
@@ -1491,9 +1647,9 @@ var init_hook_failure_signal = __esm({
1491
1647
  });
1492
1648
 
1493
1649
  // src/memory-embedder-tokenizer.ts
1494
- import { readFileSync as readFileSync4 } from "fs";
1650
+ import { readFileSync as readFileSync5 } from "fs";
1495
1651
  function loadVocab(vocabPath) {
1496
- const lines = readFileSync4(vocabPath, "utf-8").split("\n");
1652
+ const lines = readFileSync5(vocabPath, "utf-8").split("\n");
1497
1653
  const vocab = /* @__PURE__ */ new Map();
1498
1654
  for (let i = 0; i < lines.length; i++) {
1499
1655
  const tok = lines[i].replace(/\r$/, "");
@@ -1613,9 +1769,9 @@ var init_memory_embedder_tokenizer = __esm({
1613
1769
 
1614
1770
  // src/memory-embedder.ts
1615
1771
  import { fileURLToPath } from "url";
1616
- import { dirname as dirname6, join as join4 } from "path";
1617
- import { existsSync as existsSync6 } from "fs";
1618
- import { createRequire as createRequire2 } from "module";
1772
+ import { dirname as dirname6, join as join5 } from "path";
1773
+ import { existsSync as existsSync7 } from "fs";
1774
+ import { createRequire as createRequire3 } from "module";
1619
1775
  function getActiveEmbedModel() {
1620
1776
  return _activeModel;
1621
1777
  }
@@ -1639,7 +1795,7 @@ function resolveModelDir() {
1639
1795
  let dir = dirname6(fileURLToPath(import.meta.url));
1640
1796
  let root = null;
1641
1797
  for (let i = 0; i < 8; i++) {
1642
- if (existsSync6(join4(dir, "package.json"))) {
1798
+ if (existsSync7(join5(dir, "package.json"))) {
1643
1799
  root = dir;
1644
1800
  break;
1645
1801
  }
@@ -1649,8 +1805,8 @@ function resolveModelDir() {
1649
1805
  }
1650
1806
  if (!root) return null;
1651
1807
  for (const rel of ["dist/embedder", "assets/embedder"]) {
1652
- const candidate = join4(root, rel);
1653
- if (existsSync6(join4(candidate, "model_quantized.onnx"))) return candidate;
1808
+ const candidate = join5(root, rel);
1809
+ if (existsSync7(join5(candidate, "model_quantized.onnx"))) return candidate;
1654
1810
  }
1655
1811
  return null;
1656
1812
  } catch {
@@ -1659,8 +1815,8 @@ function resolveModelDir() {
1659
1815
  }
1660
1816
  function resolveWasmDir() {
1661
1817
  try {
1662
- const req2 = createRequire2(import.meta.url);
1663
- const main2 = req2.resolve(ORT_PKG);
1818
+ const req3 = createRequire3(import.meta.url);
1819
+ const main2 = req3.resolve(ORT_PKG);
1664
1820
  return dirname6(main2);
1665
1821
  } catch {
1666
1822
  return null;
@@ -1681,8 +1837,8 @@ async function getSession() {
1681
1837
  ort.env.wasm.proxy = false;
1682
1838
  ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
1683
1839
  _ortTensor = ort.Tensor;
1684
- const modelPath = join4(modelDir, "model_quantized.onnx");
1685
- const vocabPath = join4(modelDir, "vocab.txt");
1840
+ const modelPath = join5(modelDir, "model_quantized.onnx");
1841
+ const vocabPath = join5(modelDir, "vocab.txt");
1686
1842
  _vocab = loadVocab(vocabPath);
1687
1843
  const session = await ort.InferenceSession.create(modelPath, {
1688
1844
  executionProviders: ["wasm"],
@@ -2003,6 +2159,7 @@ __export(memory_db_exports, {
2003
2159
  migrateAuditLogCheckExtension: () => migrateAuditLogCheckExtension,
2004
2160
  migrateMemoryFilesFor4B: () => migrateMemoryFilesFor4B,
2005
2161
  migrateObservationEmbeddingChunks: () => migrateObservationEmbeddingChunks,
2162
+ migrateSharedMemoryFor5B: () => migrateSharedMemoryFor5B,
2006
2163
  pruneOldConversationTurns: () => pruneOldConversationTurns,
2007
2164
  pruneOldObservations: () => pruneOldObservations,
2008
2165
  pruneToolCostEvents: () => pruneToolCostEvents,
@@ -2020,7 +2177,7 @@ __export(memory_db_exports, {
2020
2177
  usageWarmupElapsed: () => usageWarmupElapsed
2021
2178
  });
2022
2179
  import { dirname as dirname7, basename as basename2 } from "path";
2023
- import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
2180
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
2024
2181
  function sanitizeFts5Query(raw) {
2025
2182
  const trimmed = raw.trim();
2026
2183
  if (!trimmed) return '""';
@@ -2037,11 +2194,11 @@ function sanitizeFts5QueryOr(raw) {
2037
2194
  function getMemoryDb() {
2038
2195
  const dbPath = getResolvedPaths().memoryDbPath;
2039
2196
  const dir = dirname7(dbPath);
2040
- if (!existsSync7(dir)) {
2197
+ if (!existsSync8(dir)) {
2041
2198
  mkdirSync6(dir, { recursive: true });
2042
2199
  }
2043
- const preExisting = existsSync7(dbPath);
2044
- const db = openDatabase(dbPath);
2200
+ const preExisting = existsSync8(dbPath);
2201
+ const db = openDatabase2(dbPath);
2045
2202
  db.pragma("journal_mode = WAL");
2046
2203
  db.pragma("foreign_keys = ON");
2047
2204
  const onDisk = db.pragma("user_version", { simple: true });
@@ -2117,6 +2274,39 @@ function migrateMemoryFilesFor4B(db) {
2117
2274
  db.exec(`ALTER TABLE ${add.table} ADD COLUMN ${add.name} ${add.decl}`);
2118
2275
  }
2119
2276
  }
2277
+ function migrateSharedMemoryFor5B(db) {
2278
+ const cols = db.prepare(`PRAGMA table_info(observations)`).all();
2279
+ if (cols.length > 0 && !cols.some((c) => c.name === "shareable")) {
2280
+ db.exec(`ALTER TABLE observations ADD COLUMN shareable INTEGER NOT NULL DEFAULT 0`);
2281
+ }
2282
+ db.exec(`
2283
+ CREATE TABLE IF NOT EXISTS shared_memory_pending (
2284
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2285
+ record_hash TEXT NOT NULL UNIQUE, -- identity + idempotency key (hex sha256)
2286
+ origin_repo_id TEXT NOT NULL, -- the signed origin repo_id (v4 UUID)
2287
+ origin_repo_label TEXT NOT NULL, -- re-slugged on read; NEVER trusted from the wire
2288
+ envelope_raw TEXT NOT NULL, -- the VERBATIM signed bytes; B-05 re-verifies these
2289
+ record_json TEXT NOT NULL, -- the single record's canonical JSON
2290
+ received_at_epoch INTEGER NOT NULL, -- epoch SECONDS (Slice-2 convention)
2291
+ accepted_at_epoch INTEGER, -- set on accept (B-05); NULL while pending
2292
+ refused_at_epoch INTEGER, -- set on refuse (B-06)
2293
+ expired_at_epoch INTEGER -- revocation \u21D2 EXPIRE, never DELETE (B-07 / S-3)
2294
+ );
2295
+ CREATE INDEX IF NOT EXISTS idx_smp_origin ON shared_memory_pending(origin_repo_id);
2296
+ CREATE INDEX IF NOT EXISTS idx_smp_received ON shared_memory_pending(received_at_epoch DESC);
2297
+ `);
2298
+ db.exec(`
2299
+ CREATE TABLE IF NOT EXISTS shared_memory_outbound (
2300
+ record_hash TEXT PRIMARY KEY, -- the exported record's canonical hash
2301
+ observation_id INTEGER NOT NULL, -- the source observations.id (for B-07 revocation)
2302
+ origin_repo_id TEXT NOT NULL, -- this repo's own repo_id at export time
2303
+ seq INTEGER NOT NULL, -- the envelope seq this record first went out in
2304
+ exported_at_epoch INTEGER NOT NULL, -- epoch SECONDS
2305
+ revoked_at_epoch INTEGER -- set when the source row is superseded/expired (B-07)
2306
+ );
2307
+ CREATE INDEX IF NOT EXISTS idx_smo_obs ON shared_memory_outbound(observation_id);
2308
+ `);
2309
+ }
2120
2310
  function migrateAuditLogCheckExtension(db) {
2121
2311
  const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
2122
2312
  if (!row) return;
@@ -2139,7 +2329,16 @@ function migrateAuditLogCheckExtension(db) {
2139
2329
  "memory_file_adopted_human",
2140
2330
  "memory_file_rendered",
2141
2331
  "memory_file_render_refused",
2142
- "memory_file_tombstoned"
2332
+ "memory_file_tombstoned",
2333
+ // B-10 (Slice 5) — the seven cross-repo shared-memory events. An existing DB whose
2334
+ // CHECK lacks any of these triggers the table rebuild below.
2335
+ "shared_memory_exported",
2336
+ "shared_memory_export_refused",
2337
+ "shared_memory_imported",
2338
+ "shared_memory_dropped",
2339
+ "shared_memory_accepted",
2340
+ "shared_memory_refused",
2341
+ "shared_memory_revoked"
2143
2342
  ];
2144
2343
  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);
2145
2344
  if (checkClauseMatch) {
@@ -2158,7 +2357,12 @@ function migrateAuditLogCheckExtension(db) {
2158
2357
  'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
2159
2358
  'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
2160
2359
  'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
2161
- 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
2360
+ 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
2361
+ -- B-10 (Slice 5) \u2014 cross-repo shared-memory events (must mirror the CREATE +
2362
+ -- the expected[] list above, or the migration would loop rebuilding forever).
2363
+ 'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
2364
+ 'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
2365
+ 'shared_memory_revoked'
2162
2366
  )),
2163
2367
  actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
2164
2368
  model_id TEXT,
@@ -2235,6 +2439,12 @@ function initMemorySchema(db) {
2235
2439
  -- credential, or takes a snapshot. A Slice-5 synced memory arrives here with
2236
2440
  -- origin='team' and must never reach disk without CR-55's gates.
2237
2441
  origin TEXT NOT NULL DEFAULT 'local',
2442
+ -- B-01 (Slice 5, cross-repo surfacing): the per-decision SHARE opt-in. Set to 1
2443
+ -- ONLY by an explicit human act (massu memory share <id>); NEVER by a heuristic,
2444
+ -- score, LLM, hook, or consolidation pass. Export (B-02) reads WHERE shareable=1
2445
+ -- AND origin='local'. Defaults 0 so every pre-existing and machine-written row is
2446
+ -- un-shareable until a human says otherwise (fail-closed).
2447
+ shareable INTEGER NOT NULL DEFAULT 0,
2238
2448
  created_at TEXT NOT NULL,
2239
2449
  created_at_epoch INTEGER NOT NULL,
2240
2450
  FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
@@ -2508,7 +2718,13 @@ function initMemorySchema(db) {
2508
2718
  'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
2509
2719
  'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
2510
2720
  'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
2511
- 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
2721
+ 'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
2722
+ -- B-10 (Slice 5): cross-repo shared-memory observability. Without these seven,
2723
+ -- nobody could ever answer "what has crossed between my repos?" (and an ingest
2724
+ -- catch would swallow the CHECK violation, silently producing NOTHING).
2725
+ 'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
2726
+ 'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
2727
+ 'shared_memory_revoked'
2512
2728
  )),
2513
2729
  actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
2514
2730
  model_id TEXT,
@@ -2877,6 +3093,7 @@ function initMemorySchema(db) {
2877
3093
  `);
2878
3094
  migrateObservationEmbeddingChunks(db);
2879
3095
  migrateMemoryFilesFor4B(db);
3096
+ migrateSharedMemoryFor5B(db);
2880
3097
  ensureRuleCandidatesTable(db);
2881
3098
  migrateRuleDelivery(db);
2882
3099
  }
@@ -3747,7 +3964,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
3747
3964
  var init_memory_db = __esm({
3748
3965
  "src/memory-db.ts"() {
3749
3966
  "use strict";
3750
- init_sqlite_loader();
3967
+ init_db_driver();
3751
3968
  init_config();
3752
3969
  init_memory_vector();
3753
3970
  init_db_backup();
@@ -3767,29 +3984,107 @@ var init_memory_db = __esm({
3767
3984
  }
3768
3985
  });
3769
3986
 
3987
+ // src/memory-authorship.ts
3988
+ import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3989
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync7, chmodSync as chmodSync3, statSync as statSync2 } from "fs";
3990
+ import { homedir as homedir4 } from "os";
3991
+ import { resolve as resolve5 } from "path";
3992
+ function renderKeyPath(home = homedir4()) {
3993
+ return resolve5(home, ".massu", "render-key");
3994
+ }
3995
+ function readRenderKey(home = homedir4()) {
3996
+ const p = renderKeyPath(home);
3997
+ try {
3998
+ if (!existsSync9(p)) return void 0;
3999
+ const raw = readFileSync6(p);
4000
+ if (raw.length !== 32) return void 0;
4001
+ return raw;
4002
+ } catch {
4003
+ return void 0;
4004
+ }
4005
+ }
4006
+ function ensureRenderKey(home = homedir4()) {
4007
+ const existing = readRenderKey(home);
4008
+ if (existing) return existing;
4009
+ const p = renderKeyPath(home);
4010
+ try {
4011
+ const dir = resolve5(home, ".massu");
4012
+ mkdirSync7(dir, { recursive: true, mode: 448 });
4013
+ try {
4014
+ chmodSync3(dir, 448);
4015
+ } catch {
4016
+ }
4017
+ const key = randomBytes(32);
4018
+ writeFileSync3(p, key, { mode: 384 });
4019
+ try {
4020
+ chmodSync3(p, 384);
4021
+ } catch {
4022
+ }
4023
+ return key;
4024
+ } catch {
4025
+ return void 0;
4026
+ }
4027
+ }
4028
+ function mintAuthorship(body, home = homedir4()) {
4029
+ try {
4030
+ const key = ensureRenderKey(home);
4031
+ if (!key) return null;
4032
+ return createHmac("sha256", key).update(body, "utf8").digest("hex");
4033
+ } catch {
4034
+ return null;
4035
+ }
4036
+ }
4037
+ function verifyAuthorship(body, frontmatter, storeRow, home = homedir4()) {
4038
+ try {
4039
+ if (storeRow?.adopted_human_at_epoch != null) return false;
4040
+ const key = readRenderKey(home);
4041
+ if (!key) return false;
4042
+ const claimed = frontmatter?.[RENDER_MAC_KEY];
4043
+ if (typeof claimed !== "string" || claimed.length === 0) return false;
4044
+ const expected = createHmac("sha256", key).update(body, "utf8").digest("hex");
4045
+ const a = Buffer.from(claimed, "utf8");
4046
+ const b = Buffer.from(expected, "utf8");
4047
+ if (a.length !== b.length) return false;
4048
+ return timingSafeEqual(a, b);
4049
+ } catch {
4050
+ return false;
4051
+ }
4052
+ }
4053
+ function extractRenderMac(frontmatter) {
4054
+ const v = frontmatter?.[RENDER_MAC_KEY];
4055
+ return typeof v === "string" && v.length > 0 ? v : null;
4056
+ }
4057
+ var RENDER_MAC_KEY;
4058
+ var init_memory_authorship = __esm({
4059
+ "src/memory-authorship.ts"() {
4060
+ "use strict";
4061
+ RENDER_MAC_KEY = "massu_render_mac";
4062
+ }
4063
+ });
4064
+
3770
4065
  // src/lib/safe-write.ts
3771
4066
  import {
3772
- writeFileSync as writeFileSync3,
4067
+ writeFileSync as writeFileSync4,
3773
4068
  renameSync,
3774
- openSync,
3775
- fsyncSync,
3776
- closeSync,
3777
- existsSync as existsSync8,
4069
+ openSync as openSync2,
4070
+ fsyncSync as fsyncSync2,
4071
+ closeSync as closeSync2,
4072
+ existsSync as existsSync10,
3778
4073
  realpathSync,
3779
4074
  unlinkSync as unlinkSync2,
3780
4075
  accessSync as accessSync2,
3781
- statSync as statSync2,
3782
- chmodSync as chmodSync3,
4076
+ statSync as statSync3,
4077
+ chmodSync as chmodSync4,
3783
4078
  constants
3784
4079
  } from "fs";
3785
- import { dirname as dirname8, resolve as resolve5, relative, isAbsolute, basename as basename3, sep } from "path";
4080
+ import { dirname as dirname8, resolve as resolve6, relative, isAbsolute, basename as basename3, sep } from "path";
3786
4081
  import { createHash as createHash2 } from "crypto";
3787
4082
  function assertContainedIn(rootDir, candidate, opts = {}) {
3788
4083
  if (candidate.includes("\0")) {
3789
4084
  throw new PathEscapeError("path contains a NUL byte");
3790
4085
  }
3791
- const root = resolve5(rootDir);
3792
- const abs = resolve5(root, candidate);
4086
+ const root = resolve6(rootDir);
4087
+ const abs = resolve6(root, candidate);
3793
4088
  if (!opts.allowNested) {
3794
4089
  const rel = relative(root, abs);
3795
4090
  if (rel.includes(sep)) {
@@ -3808,13 +4103,13 @@ function assertContainedIn(rootDir, candidate, opts = {}) {
3808
4103
  }
3809
4104
  function isContainedIn(rootDir, candidate, opts = {}) {
3810
4105
  if (candidate.includes("\0")) return false;
3811
- const root = resolve5(rootDir);
3812
- const abs = resolve5(root, candidate);
4106
+ const root = resolve6(rootDir);
4107
+ const abs = resolve6(root, candidate);
3813
4108
  const rel = relative(root, abs);
3814
4109
  if (rel === "" && !opts.allowRoot) return false;
3815
4110
  if (rel.startsWith("..") || isAbsolute(rel)) return false;
3816
4111
  let probe = abs;
3817
- while (!existsSync8(probe) && probe.length > 1) {
4112
+ while (!existsSync10(probe) && probe.length > 1) {
3818
4113
  const parent = dirname8(probe);
3819
4114
  if (parent === probe) break;
3820
4115
  probe = parent;
@@ -3833,30 +4128,30 @@ function isContainedIn(rootDir, candidate, opts = {}) {
3833
4128
  }
3834
4129
  function atomicWriteFileSync(destPath, contents) {
3835
4130
  const dir = dirname8(destPath);
3836
- const tmp = resolve5(dir, `.${basename3(destPath)}.massu-tmp-${process.pid}`);
4131
+ const tmp = resolve6(dir, `.${basename3(destPath)}.massu-tmp-${process.pid}`);
3837
4132
  let destMode;
3838
- if (existsSync8(destPath)) {
4133
+ if (existsSync10(destPath)) {
3839
4134
  accessSync2(destPath, constants.W_OK);
3840
- destMode = statSync2(destPath).mode & 511;
4135
+ destMode = statSync3(destPath).mode & 511;
3841
4136
  }
3842
4137
  let fd;
3843
4138
  try {
3844
- writeFileSync3(tmp, contents, "utf-8");
3845
- if (destMode !== void 0) chmodSync3(tmp, destMode);
3846
- fd = openSync(tmp, "r+");
3847
- fsyncSync(fd);
3848
- closeSync(fd);
4139
+ writeFileSync4(tmp, contents, "utf-8");
4140
+ if (destMode !== void 0) chmodSync4(tmp, destMode);
4141
+ fd = openSync2(tmp, "r+");
4142
+ fsyncSync2(fd);
4143
+ closeSync2(fd);
3849
4144
  fd = void 0;
3850
4145
  renameSync(tmp, destPath);
3851
4146
  } catch (err) {
3852
4147
  if (fd !== void 0) {
3853
4148
  try {
3854
- closeSync(fd);
4149
+ closeSync2(fd);
3855
4150
  } catch {
3856
4151
  }
3857
4152
  }
3858
4153
  try {
3859
- if (existsSync8(tmp)) unlinkSync2(tmp);
4154
+ if (existsSync10(tmp)) unlinkSync2(tmp);
3860
4155
  } catch {
3861
4156
  }
3862
4157
  throw err;
@@ -3933,87 +4228,9 @@ var init_safe_write = __esm({
3933
4228
  }
3934
4229
  });
3935
4230
 
3936
- // src/memory-authorship.ts
3937
- import { createHmac, randomBytes, timingSafeEqual } from "crypto";
3938
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync7, chmodSync as chmodSync4, statSync as statSync3 } from "fs";
3939
- import { homedir as homedir4 } from "os";
3940
- import { resolve as resolve6 } from "path";
3941
- function renderKeyPath(home = homedir4()) {
3942
- return resolve6(home, ".massu", "render-key");
3943
- }
3944
- function readRenderKey(home = homedir4()) {
3945
- const p = renderKeyPath(home);
3946
- try {
3947
- if (!existsSync9(p)) return void 0;
3948
- const raw = readFileSync5(p);
3949
- if (raw.length !== 32) return void 0;
3950
- return raw;
3951
- } catch {
3952
- return void 0;
3953
- }
3954
- }
3955
- function ensureRenderKey(home = homedir4()) {
3956
- const existing = readRenderKey(home);
3957
- if (existing) return existing;
3958
- const p = renderKeyPath(home);
3959
- try {
3960
- const dir = resolve6(home, ".massu");
3961
- mkdirSync7(dir, { recursive: true, mode: 448 });
3962
- try {
3963
- chmodSync4(dir, 448);
3964
- } catch {
3965
- }
3966
- const key = randomBytes(32);
3967
- writeFileSync4(p, key, { mode: 384 });
3968
- try {
3969
- chmodSync4(p, 384);
3970
- } catch {
3971
- }
3972
- return key;
3973
- } catch {
3974
- return void 0;
3975
- }
3976
- }
3977
- function mintAuthorship(body, home = homedir4()) {
3978
- try {
3979
- const key = ensureRenderKey(home);
3980
- if (!key) return null;
3981
- return createHmac("sha256", key).update(body, "utf8").digest("hex");
3982
- } catch {
3983
- return null;
3984
- }
3985
- }
3986
- function verifyAuthorship(body, frontmatter, storeRow, home = homedir4()) {
3987
- try {
3988
- if (storeRow?.adopted_human_at_epoch != null) return false;
3989
- const key = readRenderKey(home);
3990
- if (!key) return false;
3991
- const claimed = frontmatter?.[RENDER_MAC_KEY];
3992
- if (typeof claimed !== "string" || claimed.length === 0) return false;
3993
- const expected = createHmac("sha256", key).update(body, "utf8").digest("hex");
3994
- const a = Buffer.from(claimed, "utf8");
3995
- const b = Buffer.from(expected, "utf8");
3996
- if (a.length !== b.length) return false;
3997
- return timingSafeEqual(a, b);
3998
- } catch {
3999
- return false;
4000
- }
4001
- }
4002
- function extractRenderMac(frontmatter) {
4003
- const v = frontmatter?.[RENDER_MAC_KEY];
4004
- return typeof v === "string" && v.length > 0 ? v : null;
4005
- }
4006
- var RENDER_MAC_KEY;
4007
- var init_memory_authorship = __esm({
4008
- "src/memory-authorship.ts"() {
4009
- "use strict";
4010
- RENDER_MAC_KEY = "massu_render_mac";
4011
- }
4012
- });
4013
-
4014
4231
  // src/memory-index-region.ts
4015
- import { readFileSync as readFileSync6, existsSync as existsSync10 } from "fs";
4016
- import { dirname as dirname9, join as join5 } from "path";
4232
+ import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
4233
+ import { dirname as dirname9, join as join6 } from "path";
4017
4234
  function parseRegion(content) {
4018
4235
  const begins = countOccurrences(content, BEGIN_SENTINEL);
4019
4236
  const ends = countOccurrences(content, END_SENTINEL);
@@ -4099,7 +4316,7 @@ function assertOutsideRegionUnchanged(pre, post) {
4099
4316
  }
4100
4317
  }
4101
4318
  function memoryIndexLockPath(memoryDir) {
4102
- return join5(dirname9(memoryDir), ".massu-memory-index.lock");
4319
+ return join6(dirname9(memoryDir), ".massu-memory-index.lock");
4103
4320
  }
4104
4321
  function withMemoryIndexLock(memoryDir, fn) {
4105
4322
  return withFileLockSync(memoryIndexLockPath(memoryDir), fn, {
@@ -4111,9 +4328,9 @@ function withMemoryIndexLock(memoryDir, fn) {
4111
4328
  });
4112
4329
  }
4113
4330
  function readMemoryIndex(indexPath) {
4114
- if (!existsSync10(indexPath)) return void 0;
4331
+ if (!existsSync11(indexPath)) return void 0;
4115
4332
  try {
4116
- return readFileSync6(indexPath, "utf8");
4333
+ return readFileSync7(indexPath, "utf8");
4117
4334
  } catch {
4118
4335
  return void 0;
4119
4336
  }
@@ -4149,12 +4366,24 @@ var init_memory_index_region = __esm({
4149
4366
  }
4150
4367
  });
4151
4368
 
4369
+ // src/memory-origin.ts
4370
+ function isLocalOrigin(o) {
4371
+ return o === LOCAL_ORIGIN;
4372
+ }
4373
+ var LOCAL_ORIGIN;
4374
+ var init_memory_origin = __esm({
4375
+ "src/memory-origin.ts"() {
4376
+ "use strict";
4377
+ LOCAL_ORIGIN = "local";
4378
+ }
4379
+ });
4380
+
4152
4381
  // src/rule-candidate-snapshot.ts
4153
- import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
4382
+ import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
4154
4383
  function takeSnapshots(paths) {
4155
4384
  const out = /* @__PURE__ */ new Map();
4156
4385
  for (const p of paths) {
4157
- if (existsSync11(p)) out.set(p, readFileSync7(p, "utf-8"));
4386
+ if (existsSync12(p)) out.set(p, readFileSync8(p, "utf-8"));
4158
4387
  else out.set(p, null);
4159
4388
  }
4160
4389
  return out;
@@ -4164,7 +4393,7 @@ function restoreSnapshots(snapshot) {
4164
4393
  for (const [path, content] of snapshot) {
4165
4394
  try {
4166
4395
  if (content === null) {
4167
- if (existsSync11(path)) unlinkSync3(path);
4396
+ if (existsSync12(path)) unlinkSync3(path);
4168
4397
  } else {
4169
4398
  writeFileSync5(path, content, "utf-8");
4170
4399
  }
@@ -4181,8 +4410,8 @@ var init_rule_candidate_snapshot = __esm({
4181
4410
  });
4182
4411
 
4183
4412
  // src/memory-file-ingest.ts
4184
- import { readFileSync as readFileSync9, existsSync as existsSync13, readdirSync as readdirSync2 } from "fs";
4185
- import { join as join7 } from "path";
4413
+ import { readFileSync as readFileSync10, existsSync as existsSync14, readdirSync as readdirSync2 } from "fs";
4414
+ import { join as join8 } from "path";
4186
4415
  import { parse as parseYaml2 } from "yaml";
4187
4416
  import { basename as pathBasename } from "path";
4188
4417
  import { createHash as createHash3 } from "crypto";
@@ -4288,8 +4517,8 @@ function upsertMemoryFileMirror(db, f) {
4288
4517
  return "inserted";
4289
4518
  }
4290
4519
  function ingestMemoryFile(db, sessionId, filePath) {
4291
- if (!existsSync13(filePath)) return "skipped";
4292
- const content = readFileSync9(filePath, "utf-8");
4520
+ if (!existsSync14(filePath)) return "skipped";
4521
+ const content = readFileSync10(filePath, "utf-8");
4293
4522
  if (Buffer.byteLength(content, "utf-8") > MAX_MEMORY_FILE_BYTES) {
4294
4523
  process.stderr.write(
4295
4524
  `[massu] memory file exceeds ${MAX_MEMORY_FILE_BYTES} bytes, not ingested: ${filePath}
@@ -4359,21 +4588,21 @@ ${body}` : body;
4359
4588
  }
4360
4589
  function backfillMemoryFiles(db, memoryDir, sessionId) {
4361
4590
  const stats = { inserted: 0, updated: 0, skipped: 0, total: 0 };
4362
- if (!existsSync13(memoryDir)) return stats;
4591
+ if (!existsSync14(memoryDir)) return stats;
4363
4592
  const files = readdirSync2(memoryDir).filter(
4364
4593
  (f) => f.endsWith(".md") && f !== "MEMORY.md"
4365
4594
  );
4366
4595
  stats.total = files.length;
4367
4596
  const sid = sessionId ?? `backfill-${Date.now()}`;
4368
4597
  for (const file of files) {
4369
- const result = ingestMemoryFile(db, sid, join7(memoryDir, file));
4598
+ const result = ingestMemoryFile(db, sid, join8(memoryDir, file));
4370
4599
  stats[result]++;
4371
4600
  }
4372
4601
  return stats;
4373
4602
  }
4374
4603
  function reconcileMemoryFileObservations(db, memoryDir, sessionId) {
4375
4604
  try {
4376
- if (!existsSync13(memoryDir)) return 0;
4605
+ if (!existsSync14(memoryDir)) return 0;
4377
4606
  let entries;
4378
4607
  try {
4379
4608
  entries = readdirSync2(memoryDir);
@@ -4385,7 +4614,7 @@ function reconcileMemoryFileObservations(db, memoryDir, sessionId) {
4385
4614
  for (const file of files) {
4386
4615
  liveNames.add(stripMdExtension(file));
4387
4616
  try {
4388
- const content = readFileSync9(join7(memoryDir, file), "utf-8");
4617
+ const content = readFileSync10(join8(memoryDir, file), "utf-8");
4389
4618
  const fm = readMemoryFileFrontmatter(content);
4390
4619
  if (fm && typeof fm.name === "string" && fm.name) liveNames.add(fm.name);
4391
4620
  } catch {
@@ -4539,18 +4768,18 @@ var init_memory_render_path = __esm({
4539
4768
  });
4540
4769
 
4541
4770
  // src/memory-tombstones.ts
4542
- import { readFileSync as readFileSync11, appendFileSync as appendFileSync3, existsSync as existsSync15 } from "fs";
4543
- import { join as join9 } from "path";
4771
+ import { readFileSync as readFileSync13, appendFileSync as appendFileSync3, existsSync as existsSync17 } from "fs";
4772
+ import { join as join11 } from "path";
4544
4773
  function tombstoneLedgerPath(memoryDir) {
4545
- return join9(memoryDir, TOMBSTONE_LEDGER);
4774
+ return join11(memoryDir, TOMBSTONE_LEDGER);
4546
4775
  }
4547
4776
  function readTombstones(memoryDir) {
4548
4777
  const out = /* @__PURE__ */ new Map();
4549
4778
  const p = tombstoneLedgerPath(memoryDir);
4550
- if (!existsSync15(p)) return out;
4779
+ if (!existsSync17(p)) return out;
4551
4780
  let raw;
4552
4781
  try {
4553
- raw = readFileSync11(p, "utf8");
4782
+ raw = readFileSync13(p, "utf8");
4554
4783
  } catch {
4555
4784
  return out;
4556
4785
  }
@@ -4618,26 +4847,26 @@ var init_memory_llm = __esm({
4618
4847
  // src/memory-backup.ts
4619
4848
  import {
4620
4849
  readdirSync as readdirSync3,
4621
- mkdirSync as mkdirSync10,
4850
+ mkdirSync as mkdirSync11,
4622
4851
  copyFileSync as copyFileSync2,
4623
4852
  statSync as statSync4,
4624
- existsSync as existsSync16,
4625
- rmSync as rmSync4,
4626
- readFileSync as readFileSync12
4853
+ existsSync as existsSync18,
4854
+ rmSync as rmSync5,
4855
+ readFileSync as readFileSync14
4627
4856
  } from "fs";
4628
- import { homedir as homedir6 } from "os";
4629
- import { join as join10, resolve as resolve7 } from "path";
4630
- function backupsRoot(home = homedir6()) {
4857
+ import { homedir as homedir10 } from "os";
4858
+ import { join as join12, resolve as resolve7 } from "path";
4859
+ function backupsRoot(home = homedir10()) {
4631
4860
  return resolve7(home, ".massu", "memory-backups");
4632
4861
  }
4633
4862
  function backupStamp2(nowMs) {
4634
4863
  return new Date(nowMs).toISOString().replace(/:/g, "-");
4635
4864
  }
4636
4865
  function newestMtimeMs(dir) {
4637
- if (!existsSync16(dir)) return 0;
4866
+ if (!existsSync18(dir)) return 0;
4638
4867
  let newest = 0;
4639
4868
  for (const entry of readdirSync3(dir)) {
4640
- const p = join10(dir, entry);
4869
+ const p = join12(dir, entry);
4641
4870
  try {
4642
4871
  const st = statSync4(p);
4643
4872
  if (st.isFile() && st.mtimeMs > newest) newest = st.mtimeMs;
@@ -4647,12 +4876,12 @@ function newestMtimeMs(dir) {
4647
4876
  }
4648
4877
  return newest;
4649
4878
  }
4650
- function listBackups(home = homedir6()) {
4879
+ function listBackups(home = homedir10()) {
4651
4880
  const root = backupsRoot(home);
4652
- if (!existsSync16(root)) return [];
4881
+ if (!existsSync18(root)) return [];
4653
4882
  const out = [];
4654
4883
  for (const stamp of readdirSync3(root)) {
4655
- const dir = join10(root, stamp);
4884
+ const dir = join12(root, stamp);
4656
4885
  try {
4657
4886
  const st = statSync4(dir);
4658
4887
  if (!st.isDirectory()) continue;
@@ -4663,28 +4892,28 @@ function listBackups(home = homedir6()) {
4663
4892
  }
4664
4893
  return out.sort((a, b) => b.createdMs - a.createdMs);
4665
4894
  }
4666
- function hasFreshBackup(memoryDir, home = homedir6()) {
4895
+ function hasFreshBackup(memoryDir, home = homedir10()) {
4667
4896
  const backups = listBackups(home);
4668
4897
  if (backups.length === 0) return false;
4669
4898
  const newestChange = newestMtimeMs(memoryDir);
4670
4899
  return backups[0].createdMs >= newestChange;
4671
4900
  }
4672
- function takeBackup(memoryDir, nowMs, home = homedir6(), retention = DEFAULT_RETENTION2) {
4673
- if (!existsSync16(memoryDir)) {
4901
+ function takeBackup(memoryDir, nowMs, home = homedir10(), retention = DEFAULT_RETENTION2) {
4902
+ if (!existsSync18(memoryDir)) {
4674
4903
  throw new BackupError(`memory directory does not exist: ${memoryDir}`);
4675
4904
  }
4676
4905
  const stamp = backupStamp2(nowMs);
4677
- const dir = join10(backupsRoot(home), stamp);
4906
+ const dir = join12(backupsRoot(home), stamp);
4678
4907
  try {
4679
- mkdirSync10(dir, { recursive: true, mode: 448 });
4908
+ mkdirSync11(dir, { recursive: true, mode: 448 });
4680
4909
  for (const entry of readdirSync3(memoryDir)) {
4681
- const srcPath = join10(memoryDir, entry);
4910
+ const srcPath = join12(memoryDir, entry);
4682
4911
  try {
4683
4912
  if (!statSync4(srcPath).isFile()) continue;
4684
4913
  } catch {
4685
4914
  continue;
4686
4915
  }
4687
- copyFileSync2(srcPath, join10(dir, entry));
4916
+ copyFileSync2(srcPath, join12(dir, entry));
4688
4917
  }
4689
4918
  } catch (err) {
4690
4919
  throw new BackupError(`backup failed: ${err.message}`);
@@ -4692,11 +4921,11 @@ function takeBackup(memoryDir, nowMs, home = homedir6(), retention = DEFAULT_RET
4692
4921
  pruneBackups(home, retention);
4693
4922
  return { stamp, dir, createdMs: nowMs };
4694
4923
  }
4695
- function pruneBackups(home = homedir6(), retention = DEFAULT_RETENTION2) {
4924
+ function pruneBackups(home = homedir10(), retention = DEFAULT_RETENTION2) {
4696
4925
  const backups = listBackups(home);
4697
4926
  for (const stale of backups.slice(Math.max(retention, 1))) {
4698
4927
  try {
4699
- rmSync4(stale.dir, { recursive: true, force: true });
4928
+ rmSync5(stale.dir, { recursive: true, force: true });
4700
4929
  } catch {
4701
4930
  }
4702
4931
  }
@@ -4749,9 +4978,9 @@ __export(memory_renderer_exports, {
4749
4978
  renderMemoryFiles: () => renderMemoryFiles,
4750
4979
  stripFrontmatter: () => stripFrontmatter
4751
4980
  });
4752
- import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
4753
- import { join as join11 } from "path";
4754
- import { homedir as homedir7 } from "os";
4981
+ import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
4982
+ import { join as join13 } from "path";
4983
+ import { homedir as homedir11 } from "os";
4755
4984
  import { createHash as createHash5 } from "crypto";
4756
4985
  function renderMemoryFiles(db, candidates, opts) {
4757
4986
  const config = opts.config ?? resolveMemoryFilesConfig();
@@ -4760,11 +4989,11 @@ function renderMemoryFiles(db, candidates, opts) {
4760
4989
  }
4761
4990
  const dryRun = opts.dryRun === true;
4762
4991
  const now = opts.now ?? Date.now();
4763
- const home = opts.home ?? homedir7();
4992
+ const home = opts.home ?? homedir11();
4764
4993
  const audit = opts.audit ?? (() => {
4765
4994
  });
4766
4995
  const { memoryDir } = opts;
4767
- if (!existsSync17(memoryDir)) {
4996
+ if (!existsSync19(memoryDir)) {
4768
4997
  return EMPTY("no_memory_dir");
4769
4998
  }
4770
4999
  try {
@@ -4792,7 +5021,7 @@ function renderLocked(db, candidates, o) {
4792
5021
  const ownerOf = relPathOwnerLookup(db);
4793
5022
  const planned = [];
4794
5023
  for (const c of candidates) {
4795
- if (c.origin !== "local") {
5024
+ if (!isLocalOrigin(c.origin)) {
4796
5025
  refusals.push({ name: c.name, reason: "non_local_origin", detail: c.origin || "unknown" });
4797
5026
  audit("memory_file_render_refused", { name: c.name, reason: "non_local_origin" });
4798
5027
  continue;
@@ -4832,8 +5061,8 @@ function renderLocked(db, candidates, o) {
4832
5061
  refusals.push({ name: c.name, reason: "tombstoned" });
4833
5062
  continue;
4834
5063
  }
4835
- if (existsSync17(absPath)) {
4836
- const existing = readFileSync13(absPath, "utf8");
5064
+ if (existsSync19(absPath)) {
5065
+ const existing = readFileSync15(absPath, "utf8");
4837
5066
  const fm = readMemoryFileFrontmatter(existing);
4838
5067
  const storeRow = db.prepare(
4839
5068
  `SELECT massu_authored, massu_render_mac, adopted_human_at_epoch
@@ -4882,7 +5111,7 @@ function renderLocked(db, candidates, o) {
4882
5111
  return EMPTY("backup_failed");
4883
5112
  }
4884
5113
  }
4885
- const indexPath = o.indexPath ?? join11(memoryDir, "MEMORY.md");
5114
+ const indexPath = o.indexPath ?? join13(memoryDir, "MEMORY.md");
4886
5115
  const targets = [...planned.map((p) => p.absPath), indexPath];
4887
5116
  const snapshot = takeSnapshots(targets);
4888
5117
  let bytesWritten = 0;
@@ -5036,6 +5265,7 @@ var init_memory_renderer = __esm({
5036
5265
  init_memory_authorship();
5037
5266
  init_memory_render_path();
5038
5267
  init_memory_tombstones();
5268
+ init_memory_origin();
5039
5269
  init_memory_llm();
5040
5270
  init_memory_backup();
5041
5271
  init_memory_index_region();
@@ -5056,18 +5286,21 @@ var init_memory_renderer = __esm({
5056
5286
  // src/memory-render-candidates.ts
5057
5287
  var memory_render_candidates_exports = {};
5058
5288
  __export(memory_render_candidates_exports, {
5289
+ RENDERABLE_MEMORY_TYPES: () => RENDERABLE_MEMORY_TYPES,
5059
5290
  loadRenderCandidates: () => loadRenderCandidates
5060
5291
  });
5061
5292
  function loadRenderCandidates(db) {
5062
5293
  const cfg = resolveMemoryFilesConfig();
5294
+ const typePlaceholders = RENDERABLE_MEMORY_TYPES.map(() => "?").join(", ");
5063
5295
  const rows = db.prepare(
5064
5296
  `SELECT id, title, detail, importance, COALESCE(origin, 'local') AS origin
5065
5297
  FROM observations
5066
5298
  WHERE importance >= ?
5067
5299
  AND COALESCE(expired_at_epoch, 0) = 0
5300
+ AND type IN (${typePlaceholders})
5068
5301
  ORDER BY importance DESC, created_at_epoch DESC
5069
5302
  LIMIT 50`
5070
- ).all(cfg.renderMinImportance);
5303
+ ).all(cfg.renderMinImportance, ...RENDERABLE_MEMORY_TYPES);
5071
5304
  return rows.filter((r) => !r.title.startsWith("[memory-file]")).map((r) => ({
5072
5305
  observationId: r.id,
5073
5306
  name: r.title,
@@ -5077,10 +5310,17 @@ function loadRenderCandidates(db) {
5077
5310
  origin: r.origin
5078
5311
  }));
5079
5312
  }
5313
+ var RENDERABLE_MEMORY_TYPES;
5080
5314
  var init_memory_render_candidates = __esm({
5081
5315
  "src/memory-render-candidates.ts"() {
5082
5316
  "use strict";
5083
5317
  init_memory_files_config();
5318
+ RENDERABLE_MEMORY_TYPES = [
5319
+ "decision",
5320
+ "failed_attempt",
5321
+ "incident_near_miss",
5322
+ "cr_violation"
5323
+ ];
5084
5324
  }
5085
5325
  });
5086
5326
 
@@ -5246,17 +5486,17 @@ var init_manifest_registry = __esm({
5246
5486
  });
5247
5487
 
5248
5488
  // src/detect/package-detector.ts
5249
- import { readFileSync as readFileSync14, existsSync as existsSync18, statSync as statSync5, lstatSync, readdirSync as readdirSync4 } from "fs";
5250
- import { join as join12, relative as relative2 } from "path";
5489
+ import { readFileSync as readFileSync16, existsSync as existsSync20, statSync as statSync5, lstatSync, readdirSync as readdirSync4 } from "fs";
5490
+ import { join as join14, relative as relative2 } from "path";
5251
5491
  import { parse as parseToml } from "smol-toml";
5252
5492
  function safeRead(path) {
5253
5493
  try {
5254
- if (!existsSync18(path)) return null;
5494
+ if (!existsSync20(path)) return null;
5255
5495
  const ls = lstatSync(path);
5256
5496
  if (ls.isSymbolicLink()) return null;
5257
5497
  const st = statSync5(path);
5258
5498
  if (!st.isFile()) return null;
5259
- return readFileSync14(path, "utf-8");
5499
+ return readFileSync16(path, "utf-8");
5260
5500
  } catch {
5261
5501
  return null;
5262
5502
  }
@@ -5287,7 +5527,7 @@ function parsePackageJson(path, directory, root, warnings) {
5287
5527
  const peer = Object.keys(
5288
5528
  pkg.peerDependencies ?? {}
5289
5529
  );
5290
- const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync18(join12(directory, "tsconfig.json"));
5530
+ const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync20(join14(directory, "tsconfig.json"));
5291
5531
  const language = hasTs ? "typescript" : "javascript";
5292
5532
  const scripts = Object.keys(
5293
5533
  pkg.scripts ?? {}
@@ -5713,8 +5953,8 @@ function detectManifestsInDir(dir, root, warnings) {
5713
5953
  let dirEntries = null;
5714
5954
  for (const entry of getManifestRegistry2()) {
5715
5955
  if (!entry.pattern.startsWith("*")) {
5716
- const path = join12(dir, entry.pattern);
5717
- if (!existsSync18(path)) continue;
5956
+ const path = join14(dir, entry.pattern);
5957
+ if (!existsSync20(path)) continue;
5718
5958
  const m = entry.parse(path, dir, root, warnings);
5719
5959
  if (m !== null) out.push(m);
5720
5960
  } else {
@@ -5727,8 +5967,8 @@ function detectManifestsInDir(dir, root, warnings) {
5727
5967
  }
5728
5968
  for (const fname of dirEntries) {
5729
5969
  if (!matchManifestPattern2(fname, entry.pattern)) continue;
5730
- const path = join12(dir, fname);
5731
- if (!existsSync18(path)) continue;
5970
+ const path = join14(dir, fname);
5971
+ if (!existsSync20(path)) continue;
5732
5972
  const m = entry.parse(path, dir, root, warnings);
5733
5973
  if (m !== null) out.push(m);
5734
5974
  }
@@ -5738,7 +5978,7 @@ function detectManifestsInDir(dir, root, warnings) {
5738
5978
  }
5739
5979
  function listSubdirs(dir) {
5740
5980
  try {
5741
- return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) => join12(dir, e.name));
5981
+ return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) => join14(dir, e.name));
5742
5982
  } catch {
5743
5983
  return [];
5744
5984
  }
@@ -5748,8 +5988,8 @@ function detectPackageManifests(projectRoot) {
5748
5988
  const manifests = [];
5749
5989
  manifests.push(...detectManifestsInDir(projectRoot, projectRoot, warnings));
5750
5990
  for (const ws of WORKSPACE_DIRS) {
5751
- const wsRoot = join12(projectRoot, ws);
5752
- if (!existsSync18(wsRoot)) continue;
5991
+ const wsRoot = join14(projectRoot, ws);
5992
+ if (!existsSync20(wsRoot)) continue;
5753
5993
  for (const sub of listSubdirs(wsRoot)) {
5754
5994
  manifests.push(...detectManifestsInDir(sub, projectRoot, warnings));
5755
5995
  for (const sub2 of listSubdirs(sub)) {
@@ -11928,38 +12168,38 @@ var init_source_dir_detector = __esm({
11928
12168
  });
11929
12169
 
11930
12170
  // src/detect/monorepo-detector.ts
11931
- import { readFileSync as readFileSync15, existsSync as existsSync19, statSync as statSync6, lstatSync as lstatSync2, readdirSync as readdirSync5 } from "fs";
11932
- import { join as join13, relative as relative3 } from "path";
12171
+ import { readFileSync as readFileSync17, existsSync as existsSync21, statSync as statSync6, lstatSync as lstatSync2, readdirSync as readdirSync5 } from "fs";
12172
+ import { join as join15, relative as relative3 } from "path";
11933
12173
  import { parse as parseYaml3 } from "yaml";
11934
12174
  import { parse as parseToml2 } from "smol-toml";
11935
12175
  function safeReadText(path) {
11936
12176
  try {
11937
- if (!existsSync19(path)) return null;
12177
+ if (!existsSync21(path)) return null;
11938
12178
  const ls = lstatSync2(path);
11939
12179
  if (ls.isSymbolicLink()) return null;
11940
12180
  const st = statSync6(path);
11941
12181
  if (!st.isFile()) return null;
11942
- return readFileSync15(path, "utf-8");
12182
+ return readFileSync17(path, "utf-8");
11943
12183
  } catch {
11944
12184
  return null;
11945
12185
  }
11946
12186
  }
11947
12187
  function firstManifestIn(dir) {
11948
12188
  for (const m of MANIFEST_PRIORITY) {
11949
- if (existsSync19(join13(dir, m))) return m;
12189
+ if (existsSync21(join15(dir, m))) return m;
11950
12190
  }
11951
12191
  return null;
11952
12192
  }
11953
12193
  function manifestName(dir, manifest) {
11954
12194
  try {
11955
12195
  if (manifest === "package.json") {
11956
- const raw = safeReadText(join13(dir, "package.json"));
12196
+ const raw = safeReadText(join15(dir, "package.json"));
11957
12197
  if (!raw) return null;
11958
12198
  const pkg = JSON.parse(raw);
11959
12199
  return typeof pkg.name === "string" ? pkg.name : null;
11960
12200
  }
11961
12201
  if (manifest === "pyproject.toml") {
11962
- const raw = safeReadText(join13(dir, "pyproject.toml"));
12202
+ const raw = safeReadText(join15(dir, "pyproject.toml"));
11963
12203
  if (!raw) return null;
11964
12204
  const toml = parseToml2(raw);
11965
12205
  const project = toml.project;
@@ -11970,7 +12210,7 @@ function manifestName(dir, manifest) {
11970
12210
  return null;
11971
12211
  }
11972
12212
  if (manifest === "Cargo.toml") {
11973
- const raw = safeReadText(join13(dir, "Cargo.toml"));
12213
+ const raw = safeReadText(join15(dir, "Cargo.toml"));
11974
12214
  if (!raw) return null;
11975
12215
  const toml = parseToml2(raw);
11976
12216
  const pkg = toml.package;
@@ -11978,7 +12218,7 @@ function manifestName(dir, manifest) {
11978
12218
  return null;
11979
12219
  }
11980
12220
  if (manifest === "go.mod") {
11981
- const raw = safeReadText(join13(dir, "go.mod"));
12221
+ const raw = safeReadText(join15(dir, "go.mod"));
11982
12222
  if (!raw) return null;
11983
12223
  for (const line of raw.split(/\r?\n/)) {
11984
12224
  const trimmed = line.trim();
@@ -12002,7 +12242,7 @@ function pkgFromDir(root, dir) {
12002
12242
  }
12003
12243
  function listSubdirs2(dir) {
12004
12244
  try {
12005
- return readdirSync5(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) => join13(dir, e.name));
12245
+ return readdirSync5(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) => join15(dir, e.name));
12006
12246
  } catch {
12007
12247
  return [];
12008
12248
  }
@@ -12010,8 +12250,8 @@ function listSubdirs2(dir) {
12010
12250
  function genericWorkspaces(root) {
12011
12251
  const out = [];
12012
12252
  for (const parent of CONVENTIONAL_WORKSPACE_PARENTS) {
12013
- const p = join13(root, parent);
12014
- if (!existsSync19(p)) continue;
12253
+ const p = join15(root, parent);
12254
+ if (!existsSync21(p)) continue;
12015
12255
  for (const sub of listSubdirs2(p)) {
12016
12256
  const pkg = pkgFromDir(root, sub);
12017
12257
  if (pkg) out.push(pkg);
@@ -12020,7 +12260,7 @@ function genericWorkspaces(root) {
12020
12260
  return out;
12021
12261
  }
12022
12262
  function detectYarnWorkspaces(root) {
12023
- const raw = safeReadText(join13(root, "package.json"));
12263
+ const raw = safeReadText(join15(root, "package.json"));
12024
12264
  if (!raw) return null;
12025
12265
  let pkg;
12026
12266
  try {
@@ -12035,7 +12275,7 @@ function detectYarnWorkspaces(root) {
12035
12275
  return expandWorkspaceGlobs(root, globs);
12036
12276
  }
12037
12277
  function detectPnpmWorkspaces(root) {
12038
- const raw = safeReadText(join13(root, "pnpm-workspace.yaml"));
12278
+ const raw = safeReadText(join15(root, "pnpm-workspace.yaml"));
12039
12279
  if (!raw) return null;
12040
12280
  try {
12041
12281
  const parsed = parseYaml3(raw);
@@ -12051,8 +12291,8 @@ function expandWorkspaceGlobs(root, globs) {
12051
12291
  for (const pattern of globs) {
12052
12292
  const parts = pattern.split("/");
12053
12293
  if (parts.length === 2 && (parts[1] === "*" || parts[1] === "**")) {
12054
- const parent = join13(root, parts[0]);
12055
- if (!existsSync19(parent)) continue;
12294
+ const parent = join15(root, parts[0]);
12295
+ if (!existsSync21(parent)) continue;
12056
12296
  for (const sub of listSubdirs2(parent)) {
12057
12297
  const pkg = pkgFromDir(root, sub);
12058
12298
  if (pkg && !seen.has(pkg.path)) {
@@ -12062,8 +12302,8 @@ function expandWorkspaceGlobs(root, globs) {
12062
12302
  }
12063
12303
  continue;
12064
12304
  }
12065
- const direct = join13(root, pattern);
12066
- if (existsSync19(direct)) {
12305
+ const direct = join15(root, pattern);
12306
+ if (existsSync21(direct)) {
12067
12307
  const pkg = pkgFromDir(root, direct);
12068
12308
  if (pkg && !seen.has(pkg.path)) {
12069
12309
  seen.add(pkg.path);
@@ -12074,16 +12314,16 @@ function expandWorkspaceGlobs(root, globs) {
12074
12314
  return out;
12075
12315
  }
12076
12316
  function hasTurbo(root) {
12077
- return existsSync19(join13(root, "turbo.json"));
12317
+ return existsSync21(join15(root, "turbo.json"));
12078
12318
  }
12079
12319
  function hasNx(root) {
12080
- return existsSync19(join13(root, "nx.json"));
12320
+ return existsSync21(join15(root, "nx.json"));
12081
12321
  }
12082
12322
  function hasLerna(root) {
12083
- return existsSync19(join13(root, "lerna.json"));
12323
+ return existsSync21(join15(root, "lerna.json"));
12084
12324
  }
12085
12325
  function hasBazel(root) {
12086
- return existsSync19(join13(root, "WORKSPACE")) || existsSync19(join13(root, "WORKSPACE.bazel")) || existsSync19(join13(root, "MODULE.bazel"));
12326
+ return existsSync21(join15(root, "WORKSPACE")) || existsSync21(join15(root, "WORKSPACE.bazel")) || existsSync21(join15(root, "MODULE.bazel"));
12087
12327
  }
12088
12328
  function detectMonorepo(projectRoot) {
12089
12329
  const nested = [];
@@ -12274,8 +12514,8 @@ var init_vr_command_map = __esm({
12274
12514
  });
12275
12515
 
12276
12516
  // src/detect/domain-inferrer.ts
12277
- import { existsSync as existsSync20, readdirSync as readdirSync6 } from "fs";
12278
- import { join as join14 } from "path";
12517
+ import { existsSync as existsSync22, readdirSync as readdirSync6 } from "fs";
12518
+ import { join as join16 } from "path";
12279
12519
  function titleCase(s) {
12280
12520
  if (!s) return s;
12281
12521
  return s.split(/[-_\s]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
@@ -12295,8 +12535,8 @@ function topLevelSrcSubdirs(root, sourceDirs) {
12295
12535
  const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
12296
12536
  const seen = /* @__PURE__ */ new Set();
12297
12537
  for (const rel of effective) {
12298
- const abs = join14(root, rel);
12299
- if (!existsSync20(abs)) continue;
12538
+ const abs = join16(root, rel);
12539
+ if (!existsSync22(abs)) continue;
12300
12540
  try {
12301
12541
  for (const e of readdirSync6(abs, { withFileTypes: true })) {
12302
12542
  if (!e.isDirectory()) continue;
@@ -12383,8 +12623,8 @@ var init_domain_inferrer = __esm({
12383
12623
  });
12384
12624
 
12385
12625
  // src/detect/regex-fallback.ts
12386
- import { existsSync as existsSync21, readdirSync as readdirSync7, readFileSync as readFileSync16, statSync as statSync7 } from "fs";
12387
- import { resolve as resolve8, join as join15, basename as basename4 } from "path";
12626
+ import { existsSync as existsSync23, readdirSync as readdirSync7, readFileSync as readFileSync18, statSync as statSync7 } from "fs";
12627
+ import { resolve as resolve8, join as join17, basename as basename4 } from "path";
12388
12628
  function introspectPython(detection, projectRoot) {
12389
12629
  const sourceDir = resolveSourceDir(detection, "python", projectRoot);
12390
12630
  if (!sourceDir) return null;
@@ -12551,9 +12791,9 @@ function resolveSourceDir(detection, lang, projectRoot) {
12551
12791
  if (list.length > 0) {
12552
12792
  const first = list[0];
12553
12793
  const abs = resolve8(projectRoot, first);
12554
- return existsSync21(abs) ? abs : null;
12794
+ return existsSync23(abs) ? abs : null;
12555
12795
  }
12556
- return existsSync21(projectRoot) ? projectRoot : null;
12796
+ return existsSync23(projectRoot) ? projectRoot : null;
12557
12797
  }
12558
12798
  function sampleFiles(dir, nameRegex, pathFilter) {
12559
12799
  const out = [];
@@ -12573,7 +12813,7 @@ function sampleFiles(dir, nameRegex, pathFilter) {
12573
12813
  if (entry === "__pycache__") continue;
12574
12814
  if (entry === "venv" || entry === ".venv") continue;
12575
12815
  if (entry === "dist" || entry === "build") continue;
12576
- const child = join15(path, entry);
12816
+ const child = join17(path, entry);
12577
12817
  let st;
12578
12818
  try {
12579
12819
  st = statSync7(child);
@@ -12598,7 +12838,7 @@ function readSafe(path) {
12598
12838
  try {
12599
12839
  const st = statSync7(path);
12600
12840
  if (st.size > MAX_FILE_BYTES) return null;
12601
- return readFileSync16(path, "utf-8");
12841
+ return readFileSync18(path, "utf-8");
12602
12842
  } catch {
12603
12843
  return null;
12604
12844
  }
@@ -12926,9 +13166,9 @@ function detectDrift(currentConfig, actualDetection) {
12926
13166
  }
12927
13167
  const detectedManifestPaths = new Set(actualDetection.manifests.map((m) => m.relativePath));
12928
13168
  const declaredManifestPaths = /* @__PURE__ */ new Set();
12929
- const canonical = currentConfig.canonical_paths;
12930
- if (canonical && typeof canonical.manifest_paths === "string") {
12931
- for (const p of canonical.manifest_paths.split(",").map((s) => s.trim())) {
13169
+ const canonical2 = currentConfig.canonical_paths;
13170
+ if (canonical2 && typeof canonical2.manifest_paths === "string") {
13171
+ for (const p of canonical2.manifest_paths.split(",").map((s) => s.trim())) {
12932
13172
  if (p) declaredManifestPaths.add(p);
12933
13173
  }
12934
13174
  }
@@ -12975,10 +13215,34 @@ var init_drift = __esm({
12975
13215
  // src/hooks/session-start.ts
12976
13216
  init_memory_db();
12977
13217
 
13218
+ // src/team-rule-sync.ts
13219
+ import { existsSync as existsSync13, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync8 } from "fs";
13220
+ import { homedir as homedir6 } from "os";
13221
+ import { join as join7, dirname as dirname10 } from "path";
13222
+
13223
+ // src/security/promotion-apply-mac.ts
13224
+ init_memory_authorship();
13225
+ import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
13226
+ import { homedir as homedir5 } from "os";
13227
+ var DOMAIN = "massu.promotion-apply.v1";
13228
+ function canonical(f) {
13229
+ return [DOMAIN, f.origin, f.org_id, f.prompt_hash, f.destination, f.draft_text].join("\n");
13230
+ }
13231
+ function deriveApplyKey(renderKey) {
13232
+ return createHmac2("sha256", renderKey).update(DOMAIN, "utf8").digest();
13233
+ }
13234
+ function computePromotionApplyMac(fields, home = homedir5()) {
13235
+ try {
13236
+ const key = ensureRenderKey(home);
13237
+ if (!key) return null;
13238
+ return createHmac2("sha256", deriveApplyKey(key)).update(canonical(fields), "utf8").digest("hex");
13239
+ } catch {
13240
+ return null;
13241
+ }
13242
+ }
13243
+
12978
13244
  // src/team-rule-sync.ts
12979
13245
  init_config();
12980
- import { existsSync as existsSync12, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync8 } from "fs";
12981
- import { join as join6, dirname as dirname10 } from "path";
12982
13246
 
12983
13247
  // src/license.ts
12984
13248
  init_config();
@@ -13029,13 +13293,13 @@ function verifyEd25519SignedEnvelope(key, payload) {
13029
13293
  if (typeof k !== "string") continue;
13030
13294
  canonicalObj[k] = payload[k];
13031
13295
  }
13032
- const canonical = JSON.stringify(canonicalObj, [...payloadKeys].sort());
13296
+ const canonical2 = JSON.stringify(canonicalObj, [...payloadKeys].sort());
13033
13297
  try {
13034
13298
  const der = Buffer.concat([SPKI_ED25519_PREFIX, Buffer.from(key.pubkeyBytes)]);
13035
13299
  const pubkey = createPublicKey({ key: der, format: "der", type: "spki" });
13036
13300
  const ok = cryptoVerify(
13037
13301
  null,
13038
- Buffer.from(canonical, "utf-8"),
13302
+ Buffer.from(canonical2, "utf-8"),
13039
13303
  pubkey,
13040
13304
  Buffer.from(sig, "base64")
13041
13305
  );
@@ -13248,6 +13512,7 @@ init_config();
13248
13512
 
13249
13513
  // src/rule-candidate-applier.ts
13250
13514
  init_memory_db();
13515
+ init_memory_origin();
13251
13516
 
13252
13517
  // src/rule-candidate-funnel.ts
13253
13518
  init_memory_db();
@@ -13375,11 +13640,11 @@ async function pullTeamPromotions(db, opts = {}) {
13375
13640
  result.revoked_handled += 1;
13376
13641
  continue;
13377
13642
  }
13378
- if (existsSync12(candidatePath) || alreadyApplied(db, p.prompt_hash)) {
13643
+ if (existsSync13(candidatePath) || alreadyApplied(db, p.prompt_hash)) {
13379
13644
  result.skipped += 1;
13380
13645
  continue;
13381
13646
  }
13382
- materializeCandidate(db, projectRoot, candidatePath, p, signedOrgId);
13647
+ materializeCandidate(db, projectRoot, candidatePath, p, signedOrgId, opts.home ?? homedir6());
13383
13648
  result.materialized += 1;
13384
13649
  }
13385
13650
  const serverCursor = typeof envelope.cursor === "number" ? envelope.cursor : 0;
@@ -13411,7 +13676,7 @@ function isValidWirePromotion(p) {
13411
13676
  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";
13412
13677
  }
13413
13678
  function sidecarPath(projectRoot, promptHash) {
13414
- return join6(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
13679
+ return join7(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
13415
13680
  }
13416
13681
  function alreadyApplied(db, promptHash) {
13417
13682
  try {
@@ -13425,7 +13690,7 @@ function alreadyApplied(db, promptHash) {
13425
13690
  }
13426
13691
  }
13427
13692
  function handleRevocation(db, projectRoot, candidatePath, promptHash) {
13428
- if (existsSync12(candidatePath)) {
13693
+ if (existsSync13(candidatePath)) {
13429
13694
  try {
13430
13695
  unlinkSync4(candidatePath);
13431
13696
  } catch {
@@ -13439,8 +13704,12 @@ function handleRevocation(db, projectRoot, candidatePath, promptHash) {
13439
13704
  );
13440
13705
  }
13441
13706
  }
13442
- function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
13707
+ function materializeCandidate(db, projectRoot, candidatePath, p, orgId, home) {
13443
13708
  const promptText = p.draft_text.replace(/\n+/g, " ").slice(0, 200) || `team rule ${p.prompt_hash}`;
13709
+ const applyMac = computePromotionApplyMac(
13710
+ { origin: "team", org_id: orgId, prompt_hash: p.prompt_hash, destination: p.destination, draft_text: p.draft_text },
13711
+ home
13712
+ );
13444
13713
  const sidecar = {
13445
13714
  // Standard RuleCandidatePayload fields (so `/massu-rule approve` → readCandidate
13446
13715
  // → validateCandidatePayload passes), synthesized from the promotion.
@@ -13463,6 +13732,7 @@ function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
13463
13732
  promoted_by: p.promoted_by,
13464
13733
  promoted_at: p.promoted_at,
13465
13734
  signature_verified: true,
13735
+ ...applyMac ? { apply_mac: applyMac } : {},
13466
13736
  ...p.hardened === true ? { hardened: true } : {}
13467
13737
  },
13468
13738
  // Extra fields the `/massu-rule approve` flow reads to drive the apply (the
@@ -13474,7 +13744,7 @@ function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
13474
13744
  ...p.review_attestation !== void 0 ? { publisher_review_attestation: p.review_attestation } : {}
13475
13745
  };
13476
13746
  const dir = dirname10(candidatePath);
13477
- if (!existsSync12(dir)) mkdirSync8(dir, { recursive: true });
13747
+ if (!existsSync13(dir)) mkdirSync8(dir, { recursive: true });
13478
13748
  writeFileSync6(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
13479
13749
  try {
13480
13750
  shareObservation(db, p.promoted_by, getProjectName(), "rule_promotion", promptText, {
@@ -13529,24 +13799,24 @@ function failSync(db, reason) {
13529
13799
  // src/hooks/session-start.ts
13530
13800
  init_memory_file_ingest();
13531
13801
  init_config();
13532
- init_sqlite_loader();
13533
- import { readFileSync as readFileSync17, existsSync as existsSync22 } from "fs";
13534
- import { join as join16, resolve as resolve9 } from "path";
13802
+ init_db_driver();
13803
+ import { readFileSync as readFileSync19, existsSync as existsSync24 } from "fs";
13804
+ import { join as join18, resolve as resolve9 } from "path";
13535
13805
  import { parse as parseYaml4 } from "yaml";
13536
13806
 
13537
13807
  // src/capability-advisor.ts
13538
13808
  import { createHash as createHash4 } from "crypto";
13539
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
13540
- import { homedir as homedir5 } from "os";
13541
- import { dirname as dirname11, join as join8 } from "path";
13542
- function advisorStatePath(home = homedir5()) {
13543
- return join8(home, ".massu", "advisor-state.json");
13809
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
13810
+ import { homedir as homedir7 } from "os";
13811
+ import { dirname as dirname11, join as join9 } from "path";
13812
+ function advisorStatePath(home = homedir7()) {
13813
+ return join9(home, ".massu", "advisor-state.json");
13544
13814
  }
13545
13815
  function readAdvisorState(home) {
13546
13816
  try {
13547
13817
  const p = advisorStatePath(home);
13548
- if (!existsSync14(p)) return {};
13549
- return JSON.parse(readFileSync10(p, "utf-8"));
13818
+ if (!existsSync15(p)) return {};
13819
+ return JSON.parse(readFileSync11(p, "utf-8"));
13550
13820
  } catch {
13551
13821
  return {};
13552
13822
  }
@@ -13704,6 +13974,80 @@ var localModelAdvisor = {
13704
13974
  }
13705
13975
  };
13706
13976
 
13977
+ // src/advisors/cross-repo-share-advisor.ts
13978
+ init_config();
13979
+ import { homedir as homedir9 } from "os";
13980
+
13981
+ // src/memory-repos-registry.ts
13982
+ import {
13983
+ chmodSync as chmodSync5,
13984
+ existsSync as existsSync16,
13985
+ mkdirSync as mkdirSync10,
13986
+ readFileSync as readFileSync12,
13987
+ writeFileSync as writeFileSync8
13988
+ } from "fs";
13989
+ import { homedir as homedir8 } from "os";
13990
+ import { dirname as dirname12, join as join10 } from "path";
13991
+ var REPOS_REGISTRY_VERSION = 1;
13992
+ function reposRegistryPath(home = homedir8()) {
13993
+ return join10(home, ".massu", "repos.json");
13994
+ }
13995
+ function emptyRegistry() {
13996
+ return { version: REPOS_REGISTRY_VERSION, repos: [] };
13997
+ }
13998
+ function readReposRegistry(home = homedir8()) {
13999
+ const p = reposRegistryPath(home);
14000
+ if (!existsSync16(p)) return emptyRegistry();
14001
+ try {
14002
+ const parsed = JSON.parse(readFileSync12(p, "utf-8"));
14003
+ if (!parsed || typeof parsed !== "object") return emptyRegistry();
14004
+ const r = parsed;
14005
+ if (!Array.isArray(r.repos)) return emptyRegistry();
14006
+ const repos = r.repos.filter(
14007
+ (e) => !!e && typeof e === "object" && typeof e.repo_id === "string" && typeof e.label === "string"
14008
+ );
14009
+ return { version: typeof r.version === "number" ? r.version : REPOS_REGISTRY_VERSION, repos };
14010
+ } catch {
14011
+ return emptyRegistry();
14012
+ }
14013
+ }
14014
+
14015
+ // src/advisors/cross-repo-share-advisor.ts
14016
+ var CROSS_REPO_SHARE_ADVISOR_ID = "cross-repo-sharing";
14017
+ var CROSS_REPO_SHARE_REMEDY_KEYS = ["memory.share.enabled", "memory.share.subscribe"];
14018
+ function detectShareableRepos(home) {
14019
+ const repos = readReposRegistry(home).repos.filter((r) => r.share_enabled);
14020
+ if (repos.length === 0) return null;
14021
+ const labels = repos.map((r) => r.label);
14022
+ return {
14023
+ // Fingerprint covers the set of shareable repos, so registering a NEW repo
14024
+ // re-triggers the offer (the operator's setup changed).
14025
+ fingerprint: fingerprintOf(repos.map((r) => r.repo_id)),
14026
+ render: () => renderOffer(labels)
14027
+ };
14028
+ }
14029
+ function renderOffer(repoLabels) {
14030
+ const others = repoLabels.length === 1 ? `\`${repoLabels[0]}\`` : `${repoLabels.length} of your repos`;
14031
+ return [
14032
+ "\u{1F4A1} Massu can surface a decision from one of your repos in another \u2014 off by default, local-only, zero-network.",
14033
+ ` ${others} already share memory on this machine. To let THIS repo see their accepted decisions:`,
14034
+ " 1. add the repo label(s) to `memory.share.subscribe` in massu.config.yaml",
14035
+ " 2. run `massu memory review` at your next session \u2014 you accept each decision explicitly.",
14036
+ " Nothing crosses without you accepting it, and nothing is ever an instruction. `massu memory share --help`."
14037
+ ].join("\n");
14038
+ }
14039
+ var crossRepoShareAdvisor = {
14040
+ id: CROSS_REPO_SHARE_ADVISOR_ID,
14041
+ remedyKeys: CROSS_REPO_SHARE_REMEDY_KEYS,
14042
+ isConfigured() {
14043
+ const share = getConfig().memory?.share;
14044
+ return Boolean(share?.enabled) || Array.isArray(share?.subscribe) && share.subscribe.length > 0;
14045
+ },
14046
+ async detect() {
14047
+ return detectShareableRepos(homedir9());
14048
+ }
14049
+ };
14050
+
13707
14051
  // src/hooks/session-start.ts
13708
14052
  init_consolidation_config();
13709
14053
 
@@ -13807,7 +14151,7 @@ Session memory, code intelligence, and governance are now active.
13807
14151
  }
13808
14152
  try {
13809
14153
  const consolidationCfg = resolveConsolidationConfig();
13810
- const advisorBlock = await runAdvisors([localModelAdvisor], {
14154
+ const advisorBlock = await runAdvisors([localModelAdvisor, crossRepoShareAdvisor], {
13811
14155
  enabled: consolidationCfg.enabled && consolidationCfg.suggestUpgrades,
13812
14156
  suggestIntervalDays: consolidationCfg.suggestIntervalDays
13813
14157
  });
@@ -13916,8 +14260,8 @@ async function buildContext(db, sessionId, source, tokenBudget, taskId) {
13916
14260
  }
13917
14261
  try {
13918
14262
  const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
13919
- if (existsSync22(knowledgeDbPath)) {
13920
- const kdb = openDatabase(knowledgeDbPath, { readonly: true, selfHeal: false });
14263
+ if (existsSync24(knowledgeDbPath)) {
14264
+ const kdb = openDatabase2(knowledgeDbPath, { readonly: true, selfHeal: false });
13921
14265
  try {
13922
14266
  const stats = kdb.prepare(
13923
14267
  "SELECT COUNT(*) as doc_count, MAX(indexed_at) as last_indexed FROM knowledge_documents"
@@ -14006,8 +14350,8 @@ async function buildDriftBanner() {
14006
14350
  if (process.env.MASSU_DRIFT_QUIET === "1") return "";
14007
14351
  if (watcherIsLiveAndFresh()) return "";
14008
14352
  const configPath = resolve9(process.cwd(), "massu.config.yaml");
14009
- if (!existsSync22(configPath)) return "";
14010
- const content = readFileSync17(configPath, "utf-8");
14353
+ if (!existsSync24(configPath)) return "";
14354
+ const content = readFileSync19(configPath, "utf-8");
14011
14355
  const parsed = parseYaml4(content);
14012
14356
  if (!parsed || typeof parsed !== "object") return "";
14013
14357
  const det = parsed.detection;
@@ -14050,9 +14394,9 @@ function getMemoryDir() {
14050
14394
  }
14051
14395
  function loadCorrectionsPreventionRules() {
14052
14396
  try {
14053
- const correctionsPath = join16(getResolvedPaths().memoryDir, "corrections.md");
14054
- if (!existsSync22(correctionsPath)) return [];
14055
- return parseCorrectionRules(readFileSync17(correctionsPath, "utf-8"));
14397
+ const correctionsPath = join18(getResolvedPaths().memoryDir, "corrections.md");
14398
+ if (!existsSync24(correctionsPath)) return [];
14399
+ return parseCorrectionRules(readFileSync19(correctionsPath, "utf-8"));
14056
14400
  } catch (_e) {
14057
14401
  return [];
14058
14402
  }
@@ -14060,8 +14404,8 @@ function loadCorrectionsPreventionRules() {
14060
14404
  function readWatchStateRaw(cwd) {
14061
14405
  try {
14062
14406
  const path = resolve9(cwd, ".massu", "watch-state.json");
14063
- if (!existsSync22(path)) return null;
14064
- const obj = JSON.parse(readFileSync17(path, "utf-8"));
14407
+ if (!existsSync24(path)) return null;
14408
+ const obj = JSON.parse(readFileSync19(path, "utf-8"));
14065
14409
  if (!obj || typeof obj !== "object") return null;
14066
14410
  return obj;
14067
14411
  } catch {