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