@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.
- package/CHANGELOG.md +90 -0
- package/dist/cli.js +1680 -670
- package/dist/hooks/auto-learning-pipeline.js +264 -48
- package/dist/hooks/classify-failure.js +263 -47
- package/dist/hooks/cost-tracker.js +260 -44
- package/dist/hooks/fix-detector.js +269 -53
- package/dist/hooks/incident-pipeline.js +258 -42
- package/dist/hooks/intent-suggester.js +260 -44
- package/dist/hooks/memory-recall.js +400 -50
- package/dist/hooks/post-edit-context.js +262 -46
- package/dist/hooks/post-tool-use.js +286 -170
- package/dist/hooks/pre-compact.js +260 -44
- package/dist/hooks/pre-delete-check.js +264 -48
- package/dist/hooks/pre-tool-use-gate.js +264 -48
- package/dist/hooks/quality-event.js +260 -44
- package/dist/hooks/rule-enforcement-pipeline.js +259 -43
- package/dist/hooks/security-gate.js +260 -44
- package/dist/hooks/session-end.js +1441 -339
- package/dist/hooks/session-start.js +617 -274
- package/dist/hooks/user-prompt.js +286 -70
- package/package.json +2 -2
|
@@ -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
|
|
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 (
|
|
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 (
|
|
772
|
+
if (existsSync4(resolve2(dir, "package.json"))) {
|
|
617
773
|
return dir;
|
|
618
774
|
}
|
|
619
|
-
if (
|
|
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 (
|
|
640
|
-
const content =
|
|
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
|
|
1200
|
-
import { resolve as resolve3, join as
|
|
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
|
|
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 (!
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
1411
|
+
const dest = join3(dir, `${name}-${backupStamp(nowMs)}.db`);
|
|
1257
1412
|
let src = null;
|
|
1258
1413
|
try {
|
|
1259
|
-
src =
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
1424
|
-
import { join as
|
|
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 (
|
|
1431
|
-
return
|
|
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
|
|
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
|
|
1649
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1495
1650
|
function loadVocab(vocabPath) {
|
|
1496
|
-
const lines =
|
|
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
|
|
1617
|
-
import { existsSync as
|
|
1618
|
-
import { createRequire as
|
|
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 (
|
|
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 =
|
|
1653
|
-
if (
|
|
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
|
|
1663
|
-
const main2 =
|
|
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 =
|
|
1685
|
-
const vocabPath =
|
|
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
|
|
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 (!
|
|
2196
|
+
if (!existsSync8(dir)) {
|
|
2041
2197
|
mkdirSync6(dir, { recursive: true });
|
|
2042
2198
|
}
|
|
2043
|
-
const preExisting =
|
|
2044
|
-
const db =
|
|
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
|
-
|
|
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
|
|
4066
|
+
writeFileSync as writeFileSync4,
|
|
3773
4067
|
renameSync,
|
|
3774
|
-
openSync,
|
|
3775
|
-
fsyncSync,
|
|
3776
|
-
closeSync,
|
|
3777
|
-
existsSync as
|
|
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
|
|
3782
|
-
chmodSync as
|
|
4075
|
+
statSync as statSync3,
|
|
4076
|
+
chmodSync as chmodSync4,
|
|
3783
4077
|
constants
|
|
3784
4078
|
} from "fs";
|
|
3785
|
-
import { dirname as dirname8, resolve as
|
|
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 =
|
|
3792
|
-
const abs =
|
|
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 =
|
|
3812
|
-
const abs =
|
|
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 (!
|
|
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 =
|
|
4130
|
+
const tmp = resolve6(dir, `.${basename3(destPath)}.massu-tmp-${process.pid}`);
|
|
3837
4131
|
let destMode;
|
|
3838
|
-
if (
|
|
4132
|
+
if (existsSync10(destPath)) {
|
|
3839
4133
|
accessSync2(destPath, constants.W_OK);
|
|
3840
|
-
destMode =
|
|
4134
|
+
destMode = statSync3(destPath).mode & 511;
|
|
3841
4135
|
}
|
|
3842
4136
|
let fd;
|
|
3843
4137
|
try {
|
|
3844
|
-
|
|
3845
|
-
if (destMode !== void 0)
|
|
3846
|
-
fd =
|
|
3847
|
-
|
|
3848
|
-
|
|
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
|
-
|
|
4148
|
+
closeSync2(fd);
|
|
3855
4149
|
} catch {
|
|
3856
4150
|
}
|
|
3857
4151
|
}
|
|
3858
4152
|
try {
|
|
3859
|
-
if (
|
|
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
|
|
4016
|
-
import { dirname as dirname9, join as
|
|
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
|
|
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 (!
|
|
4330
|
+
if (!existsSync11(indexPath)) return void 0;
|
|
4115
4331
|
try {
|
|
4116
|
-
return
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
4185
|
-
import { join as
|
|
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 (!
|
|
4292
|
-
const content =
|
|
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 (!
|
|
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,
|
|
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 (!
|
|
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 =
|
|
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
|
|
4543
|
-
import { join as
|
|
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
|
|
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 (!
|
|
4778
|
+
if (!existsSync17(p)) return out;
|
|
4551
4779
|
let raw;
|
|
4552
4780
|
try {
|
|
4553
|
-
raw =
|
|
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
|
|
4849
|
+
mkdirSync as mkdirSync11,
|
|
4622
4850
|
copyFileSync as copyFileSync2,
|
|
4623
4851
|
statSync as statSync4,
|
|
4624
|
-
existsSync as
|
|
4625
|
-
rmSync as
|
|
4626
|
-
readFileSync as
|
|
4852
|
+
existsSync as existsSync18,
|
|
4853
|
+
rmSync as rmSync5,
|
|
4854
|
+
readFileSync as readFileSync14
|
|
4627
4855
|
} from "fs";
|
|
4628
|
-
import { homedir as
|
|
4629
|
-
import { join as
|
|
4630
|
-
function backupsRoot(home =
|
|
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 (!
|
|
4865
|
+
if (!existsSync18(dir)) return 0;
|
|
4638
4866
|
let newest = 0;
|
|
4639
4867
|
for (const entry of readdirSync3(dir)) {
|
|
4640
|
-
const p =
|
|
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 =
|
|
4878
|
+
function listBackups(home = homedir10()) {
|
|
4651
4879
|
const root = backupsRoot(home);
|
|
4652
|
-
if (!
|
|
4880
|
+
if (!existsSync18(root)) return [];
|
|
4653
4881
|
const out = [];
|
|
4654
4882
|
for (const stamp of readdirSync3(root)) {
|
|
4655
|
-
const dir =
|
|
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 =
|
|
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 =
|
|
4673
|
-
if (!
|
|
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 =
|
|
4905
|
+
const dir = join12(backupsRoot(home), stamp);
|
|
4678
4906
|
try {
|
|
4679
|
-
|
|
4907
|
+
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
4680
4908
|
for (const entry of readdirSync3(memoryDir)) {
|
|
4681
|
-
const srcPath =
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
|
4753
|
-
import { join as
|
|
4754
|
-
import { homedir as
|
|
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 ??
|
|
4991
|
+
const home = opts.home ?? homedir11();
|
|
4764
4992
|
const audit = opts.audit ?? (() => {
|
|
4765
4993
|
});
|
|
4766
4994
|
const { memoryDir } = opts;
|
|
4767
|
-
if (!
|
|
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
|
|
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 (
|
|
4836
|
-
const existing =
|
|
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 ??
|
|
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
|
|
5250
|
-
import { join as
|
|
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 (!
|
|
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
|
|
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") ||
|
|
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 =
|
|
5717
|
-
if (!
|
|
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 =
|
|
5731
|
-
if (!
|
|
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) =>
|
|
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 =
|
|
5752
|
-
if (!
|
|
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
|
|
11932
|
-
import { join as
|
|
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 (!
|
|
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
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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) =>
|
|
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 =
|
|
12014
|
-
if (!
|
|
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(
|
|
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(
|
|
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 =
|
|
12055
|
-
if (!
|
|
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 =
|
|
12066
|
-
if (
|
|
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
|
|
12316
|
+
return existsSync21(join15(root, "turbo.json"));
|
|
12078
12317
|
}
|
|
12079
12318
|
function hasNx(root) {
|
|
12080
|
-
return
|
|
12319
|
+
return existsSync21(join15(root, "nx.json"));
|
|
12081
12320
|
}
|
|
12082
12321
|
function hasLerna(root) {
|
|
12083
|
-
return
|
|
12322
|
+
return existsSync21(join15(root, "lerna.json"));
|
|
12084
12323
|
}
|
|
12085
12324
|
function hasBazel(root) {
|
|
12086
|
-
return
|
|
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
|
|
12278
|
-
import { join as
|
|
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 =
|
|
12299
|
-
if (!
|
|
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
|
|
12387
|
-
import { resolve as resolve8, join as
|
|
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
|
|
12793
|
+
return existsSync23(abs) ? abs : null;
|
|
12555
12794
|
}
|
|
12556
|
-
return
|
|
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 =
|
|
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
|
|
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
|
|
12930
|
-
if (
|
|
12931
|
-
for (const p of
|
|
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
|
|
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(
|
|
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 (
|
|
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
|
|
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 (
|
|
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 (!
|
|
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
|
-
|
|
13533
|
-
import { readFileSync as
|
|
13534
|
-
import { join as
|
|
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
|
|
13540
|
-
import { homedir as
|
|
13541
|
-
import { dirname as dirname11, join as
|
|
13542
|
-
function advisorStatePath(home =
|
|
13543
|
-
return
|
|
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 (!
|
|
13549
|
-
return JSON.parse(
|
|
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 (
|
|
13920
|
-
const kdb =
|
|
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 (!
|
|
14010
|
-
const content =
|
|
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 =
|
|
14054
|
-
if (!
|
|
14055
|
-
return parseCorrectionRules(
|
|
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 (!
|
|
14064
|
-
const obj = JSON.parse(
|
|
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 {
|