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