@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
|
@@ -423,6 +423,136 @@ var init_sqlite_loader = __esm({
|
|
|
423
423
|
}
|
|
424
424
|
});
|
|
425
425
|
|
|
426
|
+
// src/db-driver.ts
|
|
427
|
+
import { createRequire as createRequire2 } from "module";
|
|
428
|
+
import { closeSync, existsSync as existsSync3, mkdtempSync, openSync, readFileSync as readFileSync3, rmSync as rmSync3, fsyncSync } from "fs";
|
|
429
|
+
import { tmpdir } from "os";
|
|
430
|
+
import { join as join2 } from "path";
|
|
431
|
+
function resolveDbEngine() {
|
|
432
|
+
return process.env[DB_ENGINE_ENV] === "better-sqlite3" ? "better-sqlite3" : DEFAULT_DB_ENGINE;
|
|
433
|
+
}
|
|
434
|
+
function nodeSqliteCtor() {
|
|
435
|
+
if (!_nodeCtor) _nodeCtor = req2("node:sqlite").DatabaseSync;
|
|
436
|
+
return _nodeCtor;
|
|
437
|
+
}
|
|
438
|
+
function plainRow(row) {
|
|
439
|
+
if (row == null || typeof row !== "object") return row;
|
|
440
|
+
return { ...row };
|
|
441
|
+
}
|
|
442
|
+
function openNodeSqlite(dbPath, opts) {
|
|
443
|
+
const Ctor = nodeSqliteCtor();
|
|
444
|
+
const raw = new Ctor(dbPath, {
|
|
445
|
+
open: true,
|
|
446
|
+
readOnly: !!opts.readonly,
|
|
447
|
+
// FAITHFULNESS: better-sqlite3's ACTUAL default is foreign_keys=ON (empirically
|
|
448
|
+
// verified — SQLite's own default is OFF, but bs3 enables it). massu was written +
|
|
449
|
+
// tested against that default, so the adapter must match it, or a store that relies
|
|
450
|
+
// on FK-ON-by-default (without an explicit pragma) would silently lose enforcement.
|
|
451
|
+
// Explicit `foreign_keys=OFF/ON` pragmas (e.g. memory-db bulk ops) still override.
|
|
452
|
+
enableForeignKeyConstraints: true,
|
|
453
|
+
allowExtension: false
|
|
454
|
+
});
|
|
455
|
+
let savepointSeq = 0;
|
|
456
|
+
const wrapStmt = (sql) => {
|
|
457
|
+
const st = raw.prepare(sql);
|
|
458
|
+
return {
|
|
459
|
+
run: (...p) => {
|
|
460
|
+
const r = st.run(...p);
|
|
461
|
+
return {
|
|
462
|
+
changes: typeof r.changes === "bigint" ? Number(r.changes) : r.changes,
|
|
463
|
+
lastInsertRowid: r.lastInsertRowid
|
|
464
|
+
};
|
|
465
|
+
},
|
|
466
|
+
get: (...p) => plainRow(st.get(...p)),
|
|
467
|
+
all: (...p) => st.all(...p).map(plainRow)
|
|
468
|
+
};
|
|
469
|
+
};
|
|
470
|
+
const pragma = (source, options) => {
|
|
471
|
+
const s = String(source).trim();
|
|
472
|
+
if (s.includes("=")) {
|
|
473
|
+
raw.exec(`PRAGMA ${s}`);
|
|
474
|
+
return void 0;
|
|
475
|
+
}
|
|
476
|
+
const rows = raw.prepare(`PRAGMA ${s}`).all().map(plainRow);
|
|
477
|
+
if (options?.simple) {
|
|
478
|
+
const first = rows[0];
|
|
479
|
+
return first ? Object.values(first)[0] : void 0;
|
|
480
|
+
}
|
|
481
|
+
return rows;
|
|
482
|
+
};
|
|
483
|
+
const transaction = (fn) => {
|
|
484
|
+
const run = (...args) => {
|
|
485
|
+
const nested = raw.isTransaction;
|
|
486
|
+
const name = `msp_${savepointSeq++}`;
|
|
487
|
+
if (nested) raw.exec(`SAVEPOINT ${name}`);
|
|
488
|
+
else raw.exec("BEGIN");
|
|
489
|
+
try {
|
|
490
|
+
const out = fn(...args);
|
|
491
|
+
if (nested) raw.exec(`RELEASE ${name}`);
|
|
492
|
+
else raw.exec("COMMIT");
|
|
493
|
+
return out;
|
|
494
|
+
} catch (e) {
|
|
495
|
+
if (nested) {
|
|
496
|
+
raw.exec(`ROLLBACK TO ${name}`);
|
|
497
|
+
raw.exec(`RELEASE ${name}`);
|
|
498
|
+
} else {
|
|
499
|
+
raw.exec("ROLLBACK");
|
|
500
|
+
}
|
|
501
|
+
throw e;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
return run;
|
|
505
|
+
};
|
|
506
|
+
const serialize = () => {
|
|
507
|
+
if (raw.isTransaction) {
|
|
508
|
+
throw new Error(
|
|
509
|
+
"db-driver: .serialize() (node:sqlite engine) cannot run inside an open transaction \u2014 VACUUM is forbidden in a transaction. Serialize before BEGIN, or use MASSU_DB_ENGINE=better-sqlite3 for mid-transaction snapshots."
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
const dir = mkdtempSync(join2(tmpdir(), "massu-serialize-"));
|
|
513
|
+
const out = join2(dir, "snapshot.db");
|
|
514
|
+
try {
|
|
515
|
+
raw.exec(`VACUUM INTO '${out.replace(/'/g, "''")}'`);
|
|
516
|
+
const fd = openSync(out, "r");
|
|
517
|
+
try {
|
|
518
|
+
fsyncSync(fd);
|
|
519
|
+
} finally {
|
|
520
|
+
closeSync(fd);
|
|
521
|
+
}
|
|
522
|
+
return readFileSync3(out);
|
|
523
|
+
} finally {
|
|
524
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
const handle = {
|
|
528
|
+
prepare: (sql) => wrapStmt(sql),
|
|
529
|
+
exec: (sql) => raw.exec(sql),
|
|
530
|
+
pragma,
|
|
531
|
+
transaction,
|
|
532
|
+
serialize,
|
|
533
|
+
close: () => raw.close()
|
|
534
|
+
};
|
|
535
|
+
return handle;
|
|
536
|
+
}
|
|
537
|
+
function openBetterSqlite3(dbPath, opts) {
|
|
538
|
+
return openDatabase(dbPath, opts);
|
|
539
|
+
}
|
|
540
|
+
function openDatabase2(dbPath, opts = {}) {
|
|
541
|
+
return resolveDbEngine() === "better-sqlite3" ? openBetterSqlite3(dbPath, opts) : openNodeSqlite(dbPath, opts);
|
|
542
|
+
}
|
|
543
|
+
var req2, DEFAULT_DB_ENGINE, DB_ENGINE_ENV, _nodeCtor;
|
|
544
|
+
var init_db_driver = __esm({
|
|
545
|
+
"src/db-driver.ts"() {
|
|
546
|
+
"use strict";
|
|
547
|
+
init_sqlite_loader();
|
|
548
|
+
init_sqlite_loader();
|
|
549
|
+
req2 = createRequire2(import.meta.url);
|
|
550
|
+
DEFAULT_DB_ENGINE = "node-sqlite";
|
|
551
|
+
DB_ENGINE_ENV = "MASSU_DB_ENGINE";
|
|
552
|
+
_nodeCtor = null;
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
426
556
|
// src/lib/memory-path.ts
|
|
427
557
|
function encodeMemoryDirName(projectRoot) {
|
|
428
558
|
return projectRoot.replace(/\//g, "-");
|
|
@@ -566,6 +696,32 @@ var init_config_memory_schema = __esm({
|
|
|
566
696
|
// turn of EVERY session, so an unbounded index is a permanent context tax — and the
|
|
567
697
|
// per-session cap bounds only the RATE, never the total.
|
|
568
698
|
indexMaxLines: z.number().int().min(1).default(50)
|
|
699
|
+
}).default({}),
|
|
700
|
+
// --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
|
|
701
|
+
// A decision made in one of your repos can surface in another — opt-in per
|
|
702
|
+
// decision AND opt-in per repo, signed, verified, and materialized ONLY on
|
|
703
|
+
// explicit human acceptance. Local transport is FREE and zero-network.
|
|
704
|
+
//
|
|
705
|
+
// ⛔ BOTH switches default OFF, and OFF means NOTHING EXISTS — no registry, no
|
|
706
|
+
// keys, no inbox, no behavioural difference. Two INDEPENDENT opt-ins:
|
|
707
|
+
// • enabled — may this repo EXPORT its shareable decisions? (opt-in #1)
|
|
708
|
+
// • subscribe — which repo LABELS may this repo IMPORT from? (opt-in #2)
|
|
709
|
+
// `subscribe: []` means import NOTHING. There is deliberately NO `subscribe: all`
|
|
710
|
+
// — a repo you never named is a repo Massu never reads from.
|
|
711
|
+
share: z.object({
|
|
712
|
+
enabled: z.boolean().default(false),
|
|
713
|
+
subscribe: z.array(z.string()).default([]),
|
|
714
|
+
// C-04 — recall surfacing of cross-repo memories. `enabled` defaults true but is
|
|
715
|
+
// CONDITIONAL on `subscribe` being non-empty (empty by default), so the effective
|
|
716
|
+
// default is DORMANT: with `subscribe: []` the recall hook output is byte-identical
|
|
717
|
+
// to today's. `maxCrossRepoItems` caps how many accepted cross-repo items may appear
|
|
718
|
+
// per recall block (default 1); `minScore` is an OPTIONAL strictly-higher floor for
|
|
719
|
+
// cross-repo items (they are, by construction, less relevant than local ones).
|
|
720
|
+
recall: z.object({
|
|
721
|
+
enabled: z.boolean().default(true),
|
|
722
|
+
maxCrossRepoItems: z.number().int().min(0).default(1),
|
|
723
|
+
minScore: z.number().optional()
|
|
724
|
+
}).default({})
|
|
569
725
|
}).default({})
|
|
570
726
|
}).optional();
|
|
571
727
|
}
|
|
@@ -573,7 +729,7 @@ var init_config_memory_schema = __esm({
|
|
|
573
729
|
|
|
574
730
|
// src/config.ts
|
|
575
731
|
import { resolve as resolve2, dirname as dirname3 } from "path";
|
|
576
|
-
import { existsSync as
|
|
732
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
577
733
|
import { homedir as homedir2 } from "os";
|
|
578
734
|
import { parse as parseYaml } from "yaml";
|
|
579
735
|
import { z as z2 } from "zod";
|
|
@@ -581,7 +737,7 @@ function findProjectRoot() {
|
|
|
581
737
|
const cwd = process.cwd();
|
|
582
738
|
let dir = cwd;
|
|
583
739
|
while (true) {
|
|
584
|
-
if (
|
|
740
|
+
if (existsSync4(resolve2(dir, "massu.config.yaml"))) {
|
|
585
741
|
return dir;
|
|
586
742
|
}
|
|
587
743
|
const parent = dirname3(dir);
|
|
@@ -590,10 +746,10 @@ function findProjectRoot() {
|
|
|
590
746
|
}
|
|
591
747
|
dir = cwd;
|
|
592
748
|
while (true) {
|
|
593
|
-
if (
|
|
749
|
+
if (existsSync4(resolve2(dir, "package.json"))) {
|
|
594
750
|
return dir;
|
|
595
751
|
}
|
|
596
|
-
if (
|
|
752
|
+
if (existsSync4(resolve2(dir, ".git"))) {
|
|
597
753
|
return dir;
|
|
598
754
|
}
|
|
599
755
|
const parent = dirname3(dir);
|
|
@@ -613,8 +769,8 @@ function getConfig() {
|
|
|
613
769
|
const root = getProjectRoot();
|
|
614
770
|
const configPath = resolve2(root, "massu.config.yaml");
|
|
615
771
|
let rawYaml = {};
|
|
616
|
-
if (
|
|
617
|
-
const content =
|
|
772
|
+
if (existsSync4(configPath)) {
|
|
773
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
618
774
|
rawYaml = parseYaml(content) ?? {};
|
|
619
775
|
}
|
|
620
776
|
const result = RawConfigSchema.safeParse(rawYaml);
|
|
@@ -819,7 +975,6 @@ var init_config = __esm({
|
|
|
819
975
|
custom_patterns: z2.array(CustomPatternSchema).default([])
|
|
820
976
|
}).optional(),
|
|
821
977
|
adr: z2.object({
|
|
822
|
-
detection_phrases: z2.array(z2.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
|
|
823
978
|
template: z2.string().default("default"),
|
|
824
979
|
storage: z2.string().default("database"),
|
|
825
980
|
output_dir: z2.string().default("docs/adr")
|
|
@@ -1173,26 +1328,26 @@ var init_memory_vector = __esm({
|
|
|
1173
1328
|
});
|
|
1174
1329
|
|
|
1175
1330
|
// src/db-backup.ts
|
|
1176
|
-
import { existsSync as
|
|
1177
|
-
import { resolve as resolve3, join as
|
|
1331
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1332
|
+
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
1178
1333
|
import { homedir as homedir3 } from "os";
|
|
1179
1334
|
function dbBackupsRoot(home = homedir3()) {
|
|
1180
1335
|
return resolve3(home, ".massu", "db-backups");
|
|
1181
1336
|
}
|
|
1182
1337
|
function projectBackupDir(projectRoot, home = homedir3()) {
|
|
1183
1338
|
const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
|
|
1184
|
-
return
|
|
1339
|
+
return join3(dbBackupsRoot(home), slug);
|
|
1185
1340
|
}
|
|
1186
1341
|
function backupStamp(nowMs) {
|
|
1187
1342
|
return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
|
|
1188
1343
|
}
|
|
1189
1344
|
function listDbBackups(projectRoot, home = homedir3()) {
|
|
1190
1345
|
const dir = projectBackupDir(projectRoot, home);
|
|
1191
|
-
if (!
|
|
1346
|
+
if (!existsSync5(dir)) return [];
|
|
1192
1347
|
const out = [];
|
|
1193
1348
|
for (const f of readdirSync(dir)) {
|
|
1194
1349
|
if (!f.endsWith(".db")) continue;
|
|
1195
|
-
const p =
|
|
1350
|
+
const p = join3(dir, f);
|
|
1196
1351
|
const st = statSync(p);
|
|
1197
1352
|
const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
|
|
1198
1353
|
out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
|
|
@@ -1205,13 +1360,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
|
|
|
1205
1360
|
if (backups.length === 0) return false;
|
|
1206
1361
|
const newest = backups[0];
|
|
1207
1362
|
if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
|
|
1208
|
-
if (
|
|
1363
|
+
if (existsSync5(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
|
|
1209
1364
|
return true;
|
|
1210
1365
|
}
|
|
1211
1366
|
function integrityOk(dbPath) {
|
|
1212
1367
|
let db = null;
|
|
1213
1368
|
try {
|
|
1214
|
-
db =
|
|
1369
|
+
db = openDatabase2(dbPath, { readonly: true });
|
|
1215
1370
|
const rows = db.pragma("integrity_check");
|
|
1216
1371
|
return rows.length === 1 && rows[0].integrity_check === "ok";
|
|
1217
1372
|
} catch {
|
|
@@ -1224,16 +1379,16 @@ function integrityOk(dbPath) {
|
|
|
1224
1379
|
}
|
|
1225
1380
|
}
|
|
1226
1381
|
function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
|
|
1227
|
-
if (!
|
|
1382
|
+
if (!existsSync5(dbPath)) {
|
|
1228
1383
|
throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
|
|
1229
1384
|
}
|
|
1230
1385
|
const dir = projectBackupDir(projectRoot, home);
|
|
1231
1386
|
mkdirSync4(dir, { recursive: true });
|
|
1232
1387
|
const name = basename(dbPath, ".db");
|
|
1233
|
-
const dest =
|
|
1388
|
+
const dest = join3(dir, `${name}-${backupStamp(nowMs)}.db`);
|
|
1234
1389
|
let src = null;
|
|
1235
1390
|
try {
|
|
1236
|
-
src =
|
|
1391
|
+
src = openDatabase2(dbPath, { readonly: true });
|
|
1237
1392
|
src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
1238
1393
|
} catch (err) {
|
|
1239
1394
|
throw new DbBackupError(
|
|
@@ -1272,7 +1427,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
|
|
|
1272
1427
|
}
|
|
1273
1428
|
function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
|
|
1274
1429
|
try {
|
|
1275
|
-
if (!
|
|
1430
|
+
if (!existsSync5(dbPath)) return null;
|
|
1276
1431
|
if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
|
|
1277
1432
|
return backupDb(projectRoot, dbPath, nowMs, home);
|
|
1278
1433
|
} catch (err) {
|
|
@@ -1284,7 +1439,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
|
|
|
1284
1439
|
var init_db_backup = __esm({
|
|
1285
1440
|
"src/db-backup.ts"() {
|
|
1286
1441
|
"use strict";
|
|
1287
|
-
|
|
1442
|
+
init_db_driver();
|
|
1288
1443
|
DEFAULT_RETENTION = 5;
|
|
1289
1444
|
FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
1290
1445
|
DbBackupError = class extends Error {
|
|
@@ -1397,21 +1552,21 @@ var init_rule_candidate_store = __esm({
|
|
|
1397
1552
|
});
|
|
1398
1553
|
|
|
1399
1554
|
// src/hooks/lib/hook-failure-signal.ts
|
|
1400
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as
|
|
1401
|
-
import { join as
|
|
1555
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
|
|
1556
|
+
import { join as join4, dirname as dirname5 } from "path";
|
|
1402
1557
|
function resolveFailureLogPath() {
|
|
1403
1558
|
const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
|
|
1404
1559
|
if (explicit) return explicit;
|
|
1405
1560
|
let dir = process.cwd();
|
|
1406
1561
|
for (let i = 0; i < 12; i++) {
|
|
1407
|
-
if (
|
|
1408
|
-
return
|
|
1562
|
+
if (existsSync6(join4(dir, ".massu")) || existsSync6(join4(dir, "massu.config.yaml"))) {
|
|
1563
|
+
return join4(dir, ".massu", "hook-failures.jsonl");
|
|
1409
1564
|
}
|
|
1410
1565
|
const parent = dirname5(dir);
|
|
1411
1566
|
if (parent === dir) break;
|
|
1412
1567
|
dir = parent;
|
|
1413
1568
|
}
|
|
1414
|
-
return
|
|
1569
|
+
return join4(process.cwd(), ".massu", "hook-failures.jsonl");
|
|
1415
1570
|
}
|
|
1416
1571
|
function recordHookFailure(hook, error, context) {
|
|
1417
1572
|
const record = {
|
|
@@ -1468,9 +1623,9 @@ var init_hook_failure_signal = __esm({
|
|
|
1468
1623
|
});
|
|
1469
1624
|
|
|
1470
1625
|
// src/memory-embedder-tokenizer.ts
|
|
1471
|
-
import { readFileSync as
|
|
1626
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1472
1627
|
function loadVocab(vocabPath) {
|
|
1473
|
-
const lines =
|
|
1628
|
+
const lines = readFileSync5(vocabPath, "utf-8").split("\n");
|
|
1474
1629
|
const vocab = /* @__PURE__ */ new Map();
|
|
1475
1630
|
for (let i = 0; i < lines.length; i++) {
|
|
1476
1631
|
const tok = lines[i].replace(/\r$/, "");
|
|
@@ -1590,9 +1745,9 @@ var init_memory_embedder_tokenizer = __esm({
|
|
|
1590
1745
|
|
|
1591
1746
|
// src/memory-embedder.ts
|
|
1592
1747
|
import { fileURLToPath } from "url";
|
|
1593
|
-
import { dirname as dirname6, join as
|
|
1594
|
-
import { existsSync as
|
|
1595
|
-
import { createRequire as
|
|
1748
|
+
import { dirname as dirname6, join as join5 } from "path";
|
|
1749
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1750
|
+
import { createRequire as createRequire3 } from "module";
|
|
1596
1751
|
function getActiveEmbedModel() {
|
|
1597
1752
|
return _activeModel;
|
|
1598
1753
|
}
|
|
@@ -1616,7 +1771,7 @@ function resolveModelDir() {
|
|
|
1616
1771
|
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
1617
1772
|
let root = null;
|
|
1618
1773
|
for (let i = 0; i < 8; i++) {
|
|
1619
|
-
if (
|
|
1774
|
+
if (existsSync7(join5(dir, "package.json"))) {
|
|
1620
1775
|
root = dir;
|
|
1621
1776
|
break;
|
|
1622
1777
|
}
|
|
@@ -1626,8 +1781,8 @@ function resolveModelDir() {
|
|
|
1626
1781
|
}
|
|
1627
1782
|
if (!root) return null;
|
|
1628
1783
|
for (const rel of ["dist/embedder", "assets/embedder"]) {
|
|
1629
|
-
const candidate =
|
|
1630
|
-
if (
|
|
1784
|
+
const candidate = join5(root, rel);
|
|
1785
|
+
if (existsSync7(join5(candidate, "model_quantized.onnx"))) return candidate;
|
|
1631
1786
|
}
|
|
1632
1787
|
return null;
|
|
1633
1788
|
} catch {
|
|
@@ -1636,8 +1791,8 @@ function resolveModelDir() {
|
|
|
1636
1791
|
}
|
|
1637
1792
|
function resolveWasmDir() {
|
|
1638
1793
|
try {
|
|
1639
|
-
const
|
|
1640
|
-
const main2 =
|
|
1794
|
+
const req3 = createRequire3(import.meta.url);
|
|
1795
|
+
const main2 = req3.resolve(ORT_PKG);
|
|
1641
1796
|
return dirname6(main2);
|
|
1642
1797
|
} catch {
|
|
1643
1798
|
return null;
|
|
@@ -1658,8 +1813,8 @@ async function getSession() {
|
|
|
1658
1813
|
ort.env.wasm.proxy = false;
|
|
1659
1814
|
ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
|
|
1660
1815
|
_ortTensor = ort.Tensor;
|
|
1661
|
-
const modelPath =
|
|
1662
|
-
const vocabPath =
|
|
1816
|
+
const modelPath = join5(modelDir, "model_quantized.onnx");
|
|
1817
|
+
const vocabPath = join5(modelDir, "vocab.txt");
|
|
1663
1818
|
_vocab = loadVocab(vocabPath);
|
|
1664
1819
|
const session = await ort.InferenceSession.create(modelPath, {
|
|
1665
1820
|
executionProviders: ["wasm"],
|
|
@@ -1980,6 +2135,7 @@ __export(memory_db_exports, {
|
|
|
1980
2135
|
migrateAuditLogCheckExtension: () => migrateAuditLogCheckExtension,
|
|
1981
2136
|
migrateMemoryFilesFor4B: () => migrateMemoryFilesFor4B,
|
|
1982
2137
|
migrateObservationEmbeddingChunks: () => migrateObservationEmbeddingChunks,
|
|
2138
|
+
migrateSharedMemoryFor5B: () => migrateSharedMemoryFor5B,
|
|
1983
2139
|
pruneOldConversationTurns: () => pruneOldConversationTurns,
|
|
1984
2140
|
pruneOldObservations: () => pruneOldObservations,
|
|
1985
2141
|
pruneToolCostEvents: () => pruneToolCostEvents,
|
|
@@ -1997,7 +2153,7 @@ __export(memory_db_exports, {
|
|
|
1997
2153
|
usageWarmupElapsed: () => usageWarmupElapsed
|
|
1998
2154
|
});
|
|
1999
2155
|
import { dirname as dirname7, basename as basename2 } from "path";
|
|
2000
|
-
import { existsSync as
|
|
2156
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
2001
2157
|
function sanitizeFts5Query(raw) {
|
|
2002
2158
|
const trimmed = raw.trim();
|
|
2003
2159
|
if (!trimmed) return '""';
|
|
@@ -2014,11 +2170,11 @@ function sanitizeFts5QueryOr(raw) {
|
|
|
2014
2170
|
function getMemoryDb() {
|
|
2015
2171
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
2016
2172
|
const dir = dirname7(dbPath);
|
|
2017
|
-
if (!
|
|
2173
|
+
if (!existsSync8(dir)) {
|
|
2018
2174
|
mkdirSync6(dir, { recursive: true });
|
|
2019
2175
|
}
|
|
2020
|
-
const preExisting =
|
|
2021
|
-
const db =
|
|
2176
|
+
const preExisting = existsSync8(dbPath);
|
|
2177
|
+
const db = openDatabase2(dbPath);
|
|
2022
2178
|
db.pragma("journal_mode = WAL");
|
|
2023
2179
|
db.pragma("foreign_keys = ON");
|
|
2024
2180
|
const onDisk = db.pragma("user_version", { simple: true });
|
|
@@ -2094,6 +2250,39 @@ function migrateMemoryFilesFor4B(db) {
|
|
|
2094
2250
|
db.exec(`ALTER TABLE ${add.table} ADD COLUMN ${add.name} ${add.decl}`);
|
|
2095
2251
|
}
|
|
2096
2252
|
}
|
|
2253
|
+
function migrateSharedMemoryFor5B(db) {
|
|
2254
|
+
const cols = db.prepare(`PRAGMA table_info(observations)`).all();
|
|
2255
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "shareable")) {
|
|
2256
|
+
db.exec(`ALTER TABLE observations ADD COLUMN shareable INTEGER NOT NULL DEFAULT 0`);
|
|
2257
|
+
}
|
|
2258
|
+
db.exec(`
|
|
2259
|
+
CREATE TABLE IF NOT EXISTS shared_memory_pending (
|
|
2260
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2261
|
+
record_hash TEXT NOT NULL UNIQUE, -- identity + idempotency key (hex sha256)
|
|
2262
|
+
origin_repo_id TEXT NOT NULL, -- the signed origin repo_id (v4 UUID)
|
|
2263
|
+
origin_repo_label TEXT NOT NULL, -- re-slugged on read; NEVER trusted from the wire
|
|
2264
|
+
envelope_raw TEXT NOT NULL, -- the VERBATIM signed bytes; B-05 re-verifies these
|
|
2265
|
+
record_json TEXT NOT NULL, -- the single record's canonical JSON
|
|
2266
|
+
received_at_epoch INTEGER NOT NULL, -- epoch SECONDS (Slice-2 convention)
|
|
2267
|
+
accepted_at_epoch INTEGER, -- set on accept (B-05); NULL while pending
|
|
2268
|
+
refused_at_epoch INTEGER, -- set on refuse (B-06)
|
|
2269
|
+
expired_at_epoch INTEGER -- revocation \u21D2 EXPIRE, never DELETE (B-07 / S-3)
|
|
2270
|
+
);
|
|
2271
|
+
CREATE INDEX IF NOT EXISTS idx_smp_origin ON shared_memory_pending(origin_repo_id);
|
|
2272
|
+
CREATE INDEX IF NOT EXISTS idx_smp_received ON shared_memory_pending(received_at_epoch DESC);
|
|
2273
|
+
`);
|
|
2274
|
+
db.exec(`
|
|
2275
|
+
CREATE TABLE IF NOT EXISTS shared_memory_outbound (
|
|
2276
|
+
record_hash TEXT PRIMARY KEY, -- the exported record's canonical hash
|
|
2277
|
+
observation_id INTEGER NOT NULL, -- the source observations.id (for B-07 revocation)
|
|
2278
|
+
origin_repo_id TEXT NOT NULL, -- this repo's own repo_id at export time
|
|
2279
|
+
seq INTEGER NOT NULL, -- the envelope seq this record first went out in
|
|
2280
|
+
exported_at_epoch INTEGER NOT NULL, -- epoch SECONDS
|
|
2281
|
+
revoked_at_epoch INTEGER -- set when the source row is superseded/expired (B-07)
|
|
2282
|
+
);
|
|
2283
|
+
CREATE INDEX IF NOT EXISTS idx_smo_obs ON shared_memory_outbound(observation_id);
|
|
2284
|
+
`);
|
|
2285
|
+
}
|
|
2097
2286
|
function migrateAuditLogCheckExtension(db) {
|
|
2098
2287
|
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
|
|
2099
2288
|
if (!row) return;
|
|
@@ -2116,7 +2305,16 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
2116
2305
|
"memory_file_adopted_human",
|
|
2117
2306
|
"memory_file_rendered",
|
|
2118
2307
|
"memory_file_render_refused",
|
|
2119
|
-
"memory_file_tombstoned"
|
|
2308
|
+
"memory_file_tombstoned",
|
|
2309
|
+
// B-10 (Slice 5) — the seven cross-repo shared-memory events. An existing DB whose
|
|
2310
|
+
// CHECK lacks any of these triggers the table rebuild below.
|
|
2311
|
+
"shared_memory_exported",
|
|
2312
|
+
"shared_memory_export_refused",
|
|
2313
|
+
"shared_memory_imported",
|
|
2314
|
+
"shared_memory_dropped",
|
|
2315
|
+
"shared_memory_accepted",
|
|
2316
|
+
"shared_memory_refused",
|
|
2317
|
+
"shared_memory_revoked"
|
|
2120
2318
|
];
|
|
2121
2319
|
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);
|
|
2122
2320
|
if (checkClauseMatch) {
|
|
@@ -2135,7 +2333,12 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
2135
2333
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
2136
2334
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
2137
2335
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
2138
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2336
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2337
|
+
-- B-10 (Slice 5) \u2014 cross-repo shared-memory events (must mirror the CREATE +
|
|
2338
|
+
-- the expected[] list above, or the migration would loop rebuilding forever).
|
|
2339
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2340
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2341
|
+
'shared_memory_revoked'
|
|
2139
2342
|
)),
|
|
2140
2343
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
2141
2344
|
model_id TEXT,
|
|
@@ -2212,6 +2415,12 @@ function initMemorySchema(db) {
|
|
|
2212
2415
|
-- credential, or takes a snapshot. A Slice-5 synced memory arrives here with
|
|
2213
2416
|
-- origin='team' and must never reach disk without CR-55's gates.
|
|
2214
2417
|
origin TEXT NOT NULL DEFAULT 'local',
|
|
2418
|
+
-- B-01 (Slice 5, cross-repo surfacing): the per-decision SHARE opt-in. Set to 1
|
|
2419
|
+
-- ONLY by an explicit human act (massu memory share <id>); NEVER by a heuristic,
|
|
2420
|
+
-- score, LLM, hook, or consolidation pass. Export (B-02) reads WHERE shareable=1
|
|
2421
|
+
-- AND origin='local'. Defaults 0 so every pre-existing and machine-written row is
|
|
2422
|
+
-- un-shareable until a human says otherwise (fail-closed).
|
|
2423
|
+
shareable INTEGER NOT NULL DEFAULT 0,
|
|
2215
2424
|
created_at TEXT NOT NULL,
|
|
2216
2425
|
created_at_epoch INTEGER NOT NULL,
|
|
2217
2426
|
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
@@ -2485,7 +2694,13 @@ function initMemorySchema(db) {
|
|
|
2485
2694
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
2486
2695
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
2487
2696
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
2488
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2697
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2698
|
+
-- B-10 (Slice 5): cross-repo shared-memory observability. Without these seven,
|
|
2699
|
+
-- nobody could ever answer "what has crossed between my repos?" (and an ingest
|
|
2700
|
+
-- catch would swallow the CHECK violation, silently producing NOTHING).
|
|
2701
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2702
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2703
|
+
'shared_memory_revoked'
|
|
2489
2704
|
)),
|
|
2490
2705
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
2491
2706
|
model_id TEXT,
|
|
@@ -2854,6 +3069,7 @@ function initMemorySchema(db) {
|
|
|
2854
3069
|
`);
|
|
2855
3070
|
migrateObservationEmbeddingChunks(db);
|
|
2856
3071
|
migrateMemoryFilesFor4B(db);
|
|
3072
|
+
migrateSharedMemoryFor5B(db);
|
|
2857
3073
|
ensureRuleCandidatesTable(db);
|
|
2858
3074
|
migrateRuleDelivery(db);
|
|
2859
3075
|
}
|
|
@@ -3724,7 +3940,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
|
|
|
3724
3940
|
var init_memory_db = __esm({
|
|
3725
3941
|
"src/memory-db.ts"() {
|
|
3726
3942
|
"use strict";
|
|
3727
|
-
|
|
3943
|
+
init_db_driver();
|
|
3728
3944
|
init_config();
|
|
3729
3945
|
init_memory_vector();
|
|
3730
3946
|
init_db_backup();
|
|
@@ -3745,8 +3961,8 @@ var init_memory_db = __esm({
|
|
|
3745
3961
|
});
|
|
3746
3962
|
|
|
3747
3963
|
// src/hooks/pre-delete-check.ts
|
|
3748
|
-
|
|
3749
|
-
import { existsSync as
|
|
3964
|
+
init_db_driver();
|
|
3965
|
+
import { existsSync as existsSync9 } from "fs";
|
|
3750
3966
|
|
|
3751
3967
|
// src/sentinel-db.ts
|
|
3752
3968
|
init_config();
|
|
@@ -3858,9 +4074,9 @@ var KNOWLEDGE_PROTECTED_FILES = [
|
|
|
3858
4074
|
];
|
|
3859
4075
|
function getDataDb() {
|
|
3860
4076
|
const dbPath = getResolvedPaths().dataDbPath;
|
|
3861
|
-
if (!
|
|
4077
|
+
if (!existsSync9(dbPath)) return null;
|
|
3862
4078
|
try {
|
|
3863
|
-
const db =
|
|
4079
|
+
const db = openDatabase2(dbPath, { readonly: true, selfHeal: false });
|
|
3864
4080
|
db.pragma("journal_mode = WAL");
|
|
3865
4081
|
return db;
|
|
3866
4082
|
} catch {
|