@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
|
@@ -237,6 +237,32 @@ var init_config_memory_schema = __esm({
|
|
|
237
237
|
// turn of EVERY session, so an unbounded index is a permanent context tax — and the
|
|
238
238
|
// per-session cap bounds only the RATE, never the total.
|
|
239
239
|
indexMaxLines: z.number().int().min(1).default(50)
|
|
240
|
+
}).default({}),
|
|
241
|
+
// --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
|
|
242
|
+
// A decision made in one of your repos can surface in another — opt-in per
|
|
243
|
+
// decision AND opt-in per repo, signed, verified, and materialized ONLY on
|
|
244
|
+
// explicit human acceptance. Local transport is FREE and zero-network.
|
|
245
|
+
//
|
|
246
|
+
// ⛔ BOTH switches default OFF, and OFF means NOTHING EXISTS — no registry, no
|
|
247
|
+
// keys, no inbox, no behavioural difference. Two INDEPENDENT opt-ins:
|
|
248
|
+
// • enabled — may this repo EXPORT its shareable decisions? (opt-in #1)
|
|
249
|
+
// • subscribe — which repo LABELS may this repo IMPORT from? (opt-in #2)
|
|
250
|
+
// `subscribe: []` means import NOTHING. There is deliberately NO `subscribe: all`
|
|
251
|
+
// — a repo you never named is a repo Massu never reads from.
|
|
252
|
+
share: z.object({
|
|
253
|
+
enabled: z.boolean().default(false),
|
|
254
|
+
subscribe: z.array(z.string()).default([]),
|
|
255
|
+
// C-04 — recall surfacing of cross-repo memories. `enabled` defaults true but is
|
|
256
|
+
// CONDITIONAL on `subscribe` being non-empty (empty by default), so the effective
|
|
257
|
+
// default is DORMANT: with `subscribe: []` the recall hook output is byte-identical
|
|
258
|
+
// to today's. `maxCrossRepoItems` caps how many accepted cross-repo items may appear
|
|
259
|
+
// per recall block (default 1); `minScore` is an OPTIONAL strictly-higher floor for
|
|
260
|
+
// cross-repo items (they are, by construction, less relevant than local ones).
|
|
261
|
+
recall: z.object({
|
|
262
|
+
enabled: z.boolean().default(true),
|
|
263
|
+
maxCrossRepoItems: z.number().int().min(0).default(1),
|
|
264
|
+
minScore: z.number().optional()
|
|
265
|
+
}).default({})
|
|
240
266
|
}).default({})
|
|
241
267
|
}).optional();
|
|
242
268
|
}
|
|
@@ -413,6 +439,7 @@ function getResolvedPaths() {
|
|
|
413
439
|
sessionStatePath: resolve2(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
|
|
414
440
|
sessionArchivePath: resolve2(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
|
|
415
441
|
mcpJsonPath: resolve2(root, ".mcp.json"),
|
|
442
|
+
settingsPath: resolve2(root, claudeDirName, "settings.json"),
|
|
416
443
|
settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
|
|
417
444
|
};
|
|
418
445
|
}
|
|
@@ -490,7 +517,6 @@ var init_config = __esm({
|
|
|
490
517
|
custom_patterns: z2.array(CustomPatternSchema).default([])
|
|
491
518
|
}).optional(),
|
|
492
519
|
adr: z2.object({
|
|
493
|
-
detection_phrases: z2.array(z2.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
|
|
494
520
|
template: z2.string().default("default"),
|
|
495
521
|
storage: z2.string().default("database"),
|
|
496
522
|
output_dir: z2.string().default("docs/adr")
|
|
@@ -1153,6 +1179,136 @@ var init_sqlite_loader = __esm({
|
|
|
1153
1179
|
}
|
|
1154
1180
|
});
|
|
1155
1181
|
|
|
1182
|
+
// src/db-driver.ts
|
|
1183
|
+
import { createRequire as createRequire2 } from "module";
|
|
1184
|
+
import { closeSync, existsSync as existsSync4, mkdtempSync, openSync, readFileSync as readFileSync4, rmSync as rmSync3, fsyncSync } from "fs";
|
|
1185
|
+
import { tmpdir } from "os";
|
|
1186
|
+
import { join as join2 } from "path";
|
|
1187
|
+
function resolveDbEngine() {
|
|
1188
|
+
return process.env[DB_ENGINE_ENV] === "better-sqlite3" ? "better-sqlite3" : DEFAULT_DB_ENGINE;
|
|
1189
|
+
}
|
|
1190
|
+
function nodeSqliteCtor() {
|
|
1191
|
+
if (!_nodeCtor) _nodeCtor = req2("node:sqlite").DatabaseSync;
|
|
1192
|
+
return _nodeCtor;
|
|
1193
|
+
}
|
|
1194
|
+
function plainRow(row) {
|
|
1195
|
+
if (row == null || typeof row !== "object") return row;
|
|
1196
|
+
return { ...row };
|
|
1197
|
+
}
|
|
1198
|
+
function openNodeSqlite(dbPath, opts) {
|
|
1199
|
+
const Ctor = nodeSqliteCtor();
|
|
1200
|
+
const raw = new Ctor(dbPath, {
|
|
1201
|
+
open: true,
|
|
1202
|
+
readOnly: !!opts.readonly,
|
|
1203
|
+
// FAITHFULNESS: better-sqlite3's ACTUAL default is foreign_keys=ON (empirically
|
|
1204
|
+
// verified — SQLite's own default is OFF, but bs3 enables it). massu was written +
|
|
1205
|
+
// tested against that default, so the adapter must match it, or a store that relies
|
|
1206
|
+
// on FK-ON-by-default (without an explicit pragma) would silently lose enforcement.
|
|
1207
|
+
// Explicit `foreign_keys=OFF/ON` pragmas (e.g. memory-db bulk ops) still override.
|
|
1208
|
+
enableForeignKeyConstraints: true,
|
|
1209
|
+
allowExtension: false
|
|
1210
|
+
});
|
|
1211
|
+
let savepointSeq = 0;
|
|
1212
|
+
const wrapStmt = (sql) => {
|
|
1213
|
+
const st = raw.prepare(sql);
|
|
1214
|
+
return {
|
|
1215
|
+
run: (...p) => {
|
|
1216
|
+
const r = st.run(...p);
|
|
1217
|
+
return {
|
|
1218
|
+
changes: typeof r.changes === "bigint" ? Number(r.changes) : r.changes,
|
|
1219
|
+
lastInsertRowid: r.lastInsertRowid
|
|
1220
|
+
};
|
|
1221
|
+
},
|
|
1222
|
+
get: (...p) => plainRow(st.get(...p)),
|
|
1223
|
+
all: (...p) => st.all(...p).map(plainRow)
|
|
1224
|
+
};
|
|
1225
|
+
};
|
|
1226
|
+
const pragma = (source, options) => {
|
|
1227
|
+
const s = String(source).trim();
|
|
1228
|
+
if (s.includes("=")) {
|
|
1229
|
+
raw.exec(`PRAGMA ${s}`);
|
|
1230
|
+
return void 0;
|
|
1231
|
+
}
|
|
1232
|
+
const rows = raw.prepare(`PRAGMA ${s}`).all().map(plainRow);
|
|
1233
|
+
if (options?.simple) {
|
|
1234
|
+
const first = rows[0];
|
|
1235
|
+
return first ? Object.values(first)[0] : void 0;
|
|
1236
|
+
}
|
|
1237
|
+
return rows;
|
|
1238
|
+
};
|
|
1239
|
+
const transaction = (fn) => {
|
|
1240
|
+
const run = (...args) => {
|
|
1241
|
+
const nested = raw.isTransaction;
|
|
1242
|
+
const name = `msp_${savepointSeq++}`;
|
|
1243
|
+
if (nested) raw.exec(`SAVEPOINT ${name}`);
|
|
1244
|
+
else raw.exec("BEGIN");
|
|
1245
|
+
try {
|
|
1246
|
+
const out = fn(...args);
|
|
1247
|
+
if (nested) raw.exec(`RELEASE ${name}`);
|
|
1248
|
+
else raw.exec("COMMIT");
|
|
1249
|
+
return out;
|
|
1250
|
+
} catch (e) {
|
|
1251
|
+
if (nested) {
|
|
1252
|
+
raw.exec(`ROLLBACK TO ${name}`);
|
|
1253
|
+
raw.exec(`RELEASE ${name}`);
|
|
1254
|
+
} else {
|
|
1255
|
+
raw.exec("ROLLBACK");
|
|
1256
|
+
}
|
|
1257
|
+
throw e;
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
return run;
|
|
1261
|
+
};
|
|
1262
|
+
const serialize = () => {
|
|
1263
|
+
if (raw.isTransaction) {
|
|
1264
|
+
throw new Error(
|
|
1265
|
+
"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."
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
const dir = mkdtempSync(join2(tmpdir(), "massu-serialize-"));
|
|
1269
|
+
const out = join2(dir, "snapshot.db");
|
|
1270
|
+
try {
|
|
1271
|
+
raw.exec(`VACUUM INTO '${out.replace(/'/g, "''")}'`);
|
|
1272
|
+
const fd = openSync(out, "r");
|
|
1273
|
+
try {
|
|
1274
|
+
fsyncSync(fd);
|
|
1275
|
+
} finally {
|
|
1276
|
+
closeSync(fd);
|
|
1277
|
+
}
|
|
1278
|
+
return readFileSync4(out);
|
|
1279
|
+
} finally {
|
|
1280
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
const handle = {
|
|
1284
|
+
prepare: (sql) => wrapStmt(sql),
|
|
1285
|
+
exec: (sql) => raw.exec(sql),
|
|
1286
|
+
pragma,
|
|
1287
|
+
transaction,
|
|
1288
|
+
serialize,
|
|
1289
|
+
close: () => raw.close()
|
|
1290
|
+
};
|
|
1291
|
+
return handle;
|
|
1292
|
+
}
|
|
1293
|
+
function openBetterSqlite3(dbPath, opts) {
|
|
1294
|
+
return openDatabase(dbPath, opts);
|
|
1295
|
+
}
|
|
1296
|
+
function openDatabase2(dbPath, opts = {}) {
|
|
1297
|
+
return resolveDbEngine() === "better-sqlite3" ? openBetterSqlite3(dbPath, opts) : openNodeSqlite(dbPath, opts);
|
|
1298
|
+
}
|
|
1299
|
+
var req2, DEFAULT_DB_ENGINE, DB_ENGINE_ENV, _nodeCtor;
|
|
1300
|
+
var init_db_driver = __esm({
|
|
1301
|
+
"src/db-driver.ts"() {
|
|
1302
|
+
"use strict";
|
|
1303
|
+
init_sqlite_loader();
|
|
1304
|
+
init_sqlite_loader();
|
|
1305
|
+
req2 = createRequire2(import.meta.url);
|
|
1306
|
+
DEFAULT_DB_ENGINE = "node-sqlite";
|
|
1307
|
+
DB_ENGINE_ENV = "MASSU_DB_ENGINE";
|
|
1308
|
+
_nodeCtor = null;
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1156
1312
|
// src/memory-vector.ts
|
|
1157
1313
|
function float32ToBlob(vec) {
|
|
1158
1314
|
return Buffer.from(new Uint8Array(vec.buffer, vec.byteOffset, vec.byteLength));
|
|
@@ -1173,26 +1329,26 @@ var init_memory_vector = __esm({
|
|
|
1173
1329
|
});
|
|
1174
1330
|
|
|
1175
1331
|
// src/db-backup.ts
|
|
1176
|
-
import { existsSync as
|
|
1177
|
-
import { resolve as resolve3, join as
|
|
1332
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1333
|
+
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
1178
1334
|
import { homedir as homedir3 } from "os";
|
|
1179
1335
|
function dbBackupsRoot(home = homedir3()) {
|
|
1180
1336
|
return resolve3(home, ".massu", "db-backups");
|
|
1181
1337
|
}
|
|
1182
1338
|
function projectBackupDir(projectRoot, home = homedir3()) {
|
|
1183
1339
|
const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
|
|
1184
|
-
return
|
|
1340
|
+
return join3(dbBackupsRoot(home), slug);
|
|
1185
1341
|
}
|
|
1186
1342
|
function backupStamp(nowMs) {
|
|
1187
1343
|
return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
|
|
1188
1344
|
}
|
|
1189
1345
|
function listDbBackups(projectRoot, home = homedir3()) {
|
|
1190
1346
|
const dir = projectBackupDir(projectRoot, home);
|
|
1191
|
-
if (!
|
|
1347
|
+
if (!existsSync5(dir)) return [];
|
|
1192
1348
|
const out = [];
|
|
1193
1349
|
for (const f of readdirSync(dir)) {
|
|
1194
1350
|
if (!f.endsWith(".db")) continue;
|
|
1195
|
-
const p =
|
|
1351
|
+
const p = join3(dir, f);
|
|
1196
1352
|
const st = statSync(p);
|
|
1197
1353
|
const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
|
|
1198
1354
|
out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
|
|
@@ -1205,13 +1361,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
|
|
|
1205
1361
|
if (backups.length === 0) return false;
|
|
1206
1362
|
const newest = backups[0];
|
|
1207
1363
|
if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
|
|
1208
|
-
if (
|
|
1364
|
+
if (existsSync5(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
|
|
1209
1365
|
return true;
|
|
1210
1366
|
}
|
|
1211
1367
|
function integrityOk(dbPath) {
|
|
1212
1368
|
let db = null;
|
|
1213
1369
|
try {
|
|
1214
|
-
db =
|
|
1370
|
+
db = openDatabase2(dbPath, { readonly: true });
|
|
1215
1371
|
const rows = db.pragma("integrity_check");
|
|
1216
1372
|
return rows.length === 1 && rows[0].integrity_check === "ok";
|
|
1217
1373
|
} catch {
|
|
@@ -1224,16 +1380,16 @@ function integrityOk(dbPath) {
|
|
|
1224
1380
|
}
|
|
1225
1381
|
}
|
|
1226
1382
|
function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
|
|
1227
|
-
if (!
|
|
1383
|
+
if (!existsSync5(dbPath)) {
|
|
1228
1384
|
throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
|
|
1229
1385
|
}
|
|
1230
1386
|
const dir = projectBackupDir(projectRoot, home);
|
|
1231
1387
|
mkdirSync4(dir, { recursive: true });
|
|
1232
1388
|
const name = basename(dbPath, ".db");
|
|
1233
|
-
const dest =
|
|
1389
|
+
const dest = join3(dir, `${name}-${backupStamp(nowMs)}.db`);
|
|
1234
1390
|
let src = null;
|
|
1235
1391
|
try {
|
|
1236
|
-
src =
|
|
1392
|
+
src = openDatabase2(dbPath, { readonly: true });
|
|
1237
1393
|
src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
1238
1394
|
} catch (err) {
|
|
1239
1395
|
throw new DbBackupError(
|
|
@@ -1272,7 +1428,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
|
|
|
1272
1428
|
}
|
|
1273
1429
|
function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
|
|
1274
1430
|
try {
|
|
1275
|
-
if (!
|
|
1431
|
+
if (!existsSync5(dbPath)) return null;
|
|
1276
1432
|
if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
|
|
1277
1433
|
return backupDb(projectRoot, dbPath, nowMs, home);
|
|
1278
1434
|
} catch (err) {
|
|
@@ -1284,7 +1440,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
|
|
|
1284
1440
|
var init_db_backup = __esm({
|
|
1285
1441
|
"src/db-backup.ts"() {
|
|
1286
1442
|
"use strict";
|
|
1287
|
-
|
|
1443
|
+
init_db_driver();
|
|
1288
1444
|
DEFAULT_RETENTION = 5;
|
|
1289
1445
|
FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
1290
1446
|
DbBackupError = class extends Error {
|
|
@@ -1397,21 +1553,21 @@ var init_rule_candidate_store = __esm({
|
|
|
1397
1553
|
});
|
|
1398
1554
|
|
|
1399
1555
|
// src/hooks/lib/hook-failure-signal.ts
|
|
1400
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as
|
|
1401
|
-
import { join as
|
|
1556
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
|
|
1557
|
+
import { join as join4, dirname as dirname5 } from "path";
|
|
1402
1558
|
function resolveFailureLogPath() {
|
|
1403
1559
|
const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
|
|
1404
1560
|
if (explicit) return explicit;
|
|
1405
1561
|
let dir = process.cwd();
|
|
1406
1562
|
for (let i = 0; i < 12; i++) {
|
|
1407
|
-
if (
|
|
1408
|
-
return
|
|
1563
|
+
if (existsSync6(join4(dir, ".massu")) || existsSync6(join4(dir, "massu.config.yaml"))) {
|
|
1564
|
+
return join4(dir, ".massu", "hook-failures.jsonl");
|
|
1409
1565
|
}
|
|
1410
1566
|
const parent = dirname5(dir);
|
|
1411
1567
|
if (parent === dir) break;
|
|
1412
1568
|
dir = parent;
|
|
1413
1569
|
}
|
|
1414
|
-
return
|
|
1570
|
+
return join4(process.cwd(), ".massu", "hook-failures.jsonl");
|
|
1415
1571
|
}
|
|
1416
1572
|
function recordHookFailure(hook, error, context) {
|
|
1417
1573
|
const record = {
|
|
@@ -1468,9 +1624,9 @@ var init_hook_failure_signal = __esm({
|
|
|
1468
1624
|
});
|
|
1469
1625
|
|
|
1470
1626
|
// src/memory-embedder-tokenizer.ts
|
|
1471
|
-
import { readFileSync as
|
|
1627
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1472
1628
|
function loadVocab(vocabPath) {
|
|
1473
|
-
const lines =
|
|
1629
|
+
const lines = readFileSync5(vocabPath, "utf-8").split("\n");
|
|
1474
1630
|
const vocab = /* @__PURE__ */ new Map();
|
|
1475
1631
|
for (let i = 0; i < lines.length; i++) {
|
|
1476
1632
|
const tok = lines[i].replace(/\r$/, "");
|
|
@@ -1590,9 +1746,9 @@ var init_memory_embedder_tokenizer = __esm({
|
|
|
1590
1746
|
|
|
1591
1747
|
// src/memory-embedder.ts
|
|
1592
1748
|
import { fileURLToPath } from "url";
|
|
1593
|
-
import { dirname as dirname6, join as
|
|
1594
|
-
import { existsSync as
|
|
1595
|
-
import { createRequire as
|
|
1749
|
+
import { dirname as dirname6, join as join5 } from "path";
|
|
1750
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1751
|
+
import { createRequire as createRequire3 } from "module";
|
|
1596
1752
|
function getActiveEmbedModel() {
|
|
1597
1753
|
return _activeModel;
|
|
1598
1754
|
}
|
|
@@ -1616,7 +1772,7 @@ function resolveModelDir() {
|
|
|
1616
1772
|
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
1617
1773
|
let root = null;
|
|
1618
1774
|
for (let i = 0; i < 8; i++) {
|
|
1619
|
-
if (
|
|
1775
|
+
if (existsSync7(join5(dir, "package.json"))) {
|
|
1620
1776
|
root = dir;
|
|
1621
1777
|
break;
|
|
1622
1778
|
}
|
|
@@ -1626,8 +1782,8 @@ function resolveModelDir() {
|
|
|
1626
1782
|
}
|
|
1627
1783
|
if (!root) return null;
|
|
1628
1784
|
for (const rel of ["dist/embedder", "assets/embedder"]) {
|
|
1629
|
-
const candidate =
|
|
1630
|
-
if (
|
|
1785
|
+
const candidate = join5(root, rel);
|
|
1786
|
+
if (existsSync7(join5(candidate, "model_quantized.onnx"))) return candidate;
|
|
1631
1787
|
}
|
|
1632
1788
|
return null;
|
|
1633
1789
|
} catch {
|
|
@@ -1636,8 +1792,8 @@ function resolveModelDir() {
|
|
|
1636
1792
|
}
|
|
1637
1793
|
function resolveWasmDir() {
|
|
1638
1794
|
try {
|
|
1639
|
-
const
|
|
1640
|
-
const main2 =
|
|
1795
|
+
const req3 = createRequire3(import.meta.url);
|
|
1796
|
+
const main2 = req3.resolve(ORT_PKG);
|
|
1641
1797
|
return dirname6(main2);
|
|
1642
1798
|
} catch {
|
|
1643
1799
|
return null;
|
|
@@ -1658,8 +1814,8 @@ async function getSession() {
|
|
|
1658
1814
|
ort.env.wasm.proxy = false;
|
|
1659
1815
|
ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
|
|
1660
1816
|
_ortTensor = ort.Tensor;
|
|
1661
|
-
const modelPath =
|
|
1662
|
-
const vocabPath =
|
|
1817
|
+
const modelPath = join5(modelDir, "model_quantized.onnx");
|
|
1818
|
+
const vocabPath = join5(modelDir, "vocab.txt");
|
|
1663
1819
|
_vocab = loadVocab(vocabPath);
|
|
1664
1820
|
const session = await ort.InferenceSession.create(modelPath, {
|
|
1665
1821
|
executionProviders: ["wasm"],
|
|
@@ -1980,6 +2136,7 @@ __export(memory_db_exports, {
|
|
|
1980
2136
|
migrateAuditLogCheckExtension: () => migrateAuditLogCheckExtension,
|
|
1981
2137
|
migrateMemoryFilesFor4B: () => migrateMemoryFilesFor4B,
|
|
1982
2138
|
migrateObservationEmbeddingChunks: () => migrateObservationEmbeddingChunks,
|
|
2139
|
+
migrateSharedMemoryFor5B: () => migrateSharedMemoryFor5B,
|
|
1983
2140
|
pruneOldConversationTurns: () => pruneOldConversationTurns,
|
|
1984
2141
|
pruneOldObservations: () => pruneOldObservations,
|
|
1985
2142
|
pruneToolCostEvents: () => pruneToolCostEvents,
|
|
@@ -1997,7 +2154,7 @@ __export(memory_db_exports, {
|
|
|
1997
2154
|
usageWarmupElapsed: () => usageWarmupElapsed
|
|
1998
2155
|
});
|
|
1999
2156
|
import { dirname as dirname7, basename as basename2 } from "path";
|
|
2000
|
-
import { existsSync as
|
|
2157
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
2001
2158
|
function sanitizeFts5Query(raw) {
|
|
2002
2159
|
const trimmed = raw.trim();
|
|
2003
2160
|
if (!trimmed) return '""';
|
|
@@ -2014,11 +2171,11 @@ function sanitizeFts5QueryOr(raw) {
|
|
|
2014
2171
|
function getMemoryDb() {
|
|
2015
2172
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
2016
2173
|
const dir = dirname7(dbPath);
|
|
2017
|
-
if (!
|
|
2174
|
+
if (!existsSync8(dir)) {
|
|
2018
2175
|
mkdirSync6(dir, { recursive: true });
|
|
2019
2176
|
}
|
|
2020
|
-
const preExisting =
|
|
2021
|
-
const db =
|
|
2177
|
+
const preExisting = existsSync8(dbPath);
|
|
2178
|
+
const db = openDatabase2(dbPath);
|
|
2022
2179
|
db.pragma("journal_mode = WAL");
|
|
2023
2180
|
db.pragma("foreign_keys = ON");
|
|
2024
2181
|
const onDisk = db.pragma("user_version", { simple: true });
|
|
@@ -2094,6 +2251,39 @@ function migrateMemoryFilesFor4B(db) {
|
|
|
2094
2251
|
db.exec(`ALTER TABLE ${add.table} ADD COLUMN ${add.name} ${add.decl}`);
|
|
2095
2252
|
}
|
|
2096
2253
|
}
|
|
2254
|
+
function migrateSharedMemoryFor5B(db) {
|
|
2255
|
+
const cols = db.prepare(`PRAGMA table_info(observations)`).all();
|
|
2256
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "shareable")) {
|
|
2257
|
+
db.exec(`ALTER TABLE observations ADD COLUMN shareable INTEGER NOT NULL DEFAULT 0`);
|
|
2258
|
+
}
|
|
2259
|
+
db.exec(`
|
|
2260
|
+
CREATE TABLE IF NOT EXISTS shared_memory_pending (
|
|
2261
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2262
|
+
record_hash TEXT NOT NULL UNIQUE, -- identity + idempotency key (hex sha256)
|
|
2263
|
+
origin_repo_id TEXT NOT NULL, -- the signed origin repo_id (v4 UUID)
|
|
2264
|
+
origin_repo_label TEXT NOT NULL, -- re-slugged on read; NEVER trusted from the wire
|
|
2265
|
+
envelope_raw TEXT NOT NULL, -- the VERBATIM signed bytes; B-05 re-verifies these
|
|
2266
|
+
record_json TEXT NOT NULL, -- the single record's canonical JSON
|
|
2267
|
+
received_at_epoch INTEGER NOT NULL, -- epoch SECONDS (Slice-2 convention)
|
|
2268
|
+
accepted_at_epoch INTEGER, -- set on accept (B-05); NULL while pending
|
|
2269
|
+
refused_at_epoch INTEGER, -- set on refuse (B-06)
|
|
2270
|
+
expired_at_epoch INTEGER -- revocation \u21D2 EXPIRE, never DELETE (B-07 / S-3)
|
|
2271
|
+
);
|
|
2272
|
+
CREATE INDEX IF NOT EXISTS idx_smp_origin ON shared_memory_pending(origin_repo_id);
|
|
2273
|
+
CREATE INDEX IF NOT EXISTS idx_smp_received ON shared_memory_pending(received_at_epoch DESC);
|
|
2274
|
+
`);
|
|
2275
|
+
db.exec(`
|
|
2276
|
+
CREATE TABLE IF NOT EXISTS shared_memory_outbound (
|
|
2277
|
+
record_hash TEXT PRIMARY KEY, -- the exported record's canonical hash
|
|
2278
|
+
observation_id INTEGER NOT NULL, -- the source observations.id (for B-07 revocation)
|
|
2279
|
+
origin_repo_id TEXT NOT NULL, -- this repo's own repo_id at export time
|
|
2280
|
+
seq INTEGER NOT NULL, -- the envelope seq this record first went out in
|
|
2281
|
+
exported_at_epoch INTEGER NOT NULL, -- epoch SECONDS
|
|
2282
|
+
revoked_at_epoch INTEGER -- set when the source row is superseded/expired (B-07)
|
|
2283
|
+
);
|
|
2284
|
+
CREATE INDEX IF NOT EXISTS idx_smo_obs ON shared_memory_outbound(observation_id);
|
|
2285
|
+
`);
|
|
2286
|
+
}
|
|
2097
2287
|
function migrateAuditLogCheckExtension(db) {
|
|
2098
2288
|
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
|
|
2099
2289
|
if (!row) return;
|
|
@@ -2116,7 +2306,16 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
2116
2306
|
"memory_file_adopted_human",
|
|
2117
2307
|
"memory_file_rendered",
|
|
2118
2308
|
"memory_file_render_refused",
|
|
2119
|
-
"memory_file_tombstoned"
|
|
2309
|
+
"memory_file_tombstoned",
|
|
2310
|
+
// B-10 (Slice 5) — the seven cross-repo shared-memory events. An existing DB whose
|
|
2311
|
+
// CHECK lacks any of these triggers the table rebuild below.
|
|
2312
|
+
"shared_memory_exported",
|
|
2313
|
+
"shared_memory_export_refused",
|
|
2314
|
+
"shared_memory_imported",
|
|
2315
|
+
"shared_memory_dropped",
|
|
2316
|
+
"shared_memory_accepted",
|
|
2317
|
+
"shared_memory_refused",
|
|
2318
|
+
"shared_memory_revoked"
|
|
2120
2319
|
];
|
|
2121
2320
|
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
2321
|
if (checkClauseMatch) {
|
|
@@ -2135,7 +2334,12 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
2135
2334
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
2136
2335
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
2137
2336
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
2138
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2337
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2338
|
+
-- B-10 (Slice 5) \u2014 cross-repo shared-memory events (must mirror the CREATE +
|
|
2339
|
+
-- the expected[] list above, or the migration would loop rebuilding forever).
|
|
2340
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2341
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2342
|
+
'shared_memory_revoked'
|
|
2139
2343
|
)),
|
|
2140
2344
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
2141
2345
|
model_id TEXT,
|
|
@@ -2212,6 +2416,12 @@ function initMemorySchema(db) {
|
|
|
2212
2416
|
-- credential, or takes a snapshot. A Slice-5 synced memory arrives here with
|
|
2213
2417
|
-- origin='team' and must never reach disk without CR-55's gates.
|
|
2214
2418
|
origin TEXT NOT NULL DEFAULT 'local',
|
|
2419
|
+
-- B-01 (Slice 5, cross-repo surfacing): the per-decision SHARE opt-in. Set to 1
|
|
2420
|
+
-- ONLY by an explicit human act (massu memory share <id>); NEVER by a heuristic,
|
|
2421
|
+
-- score, LLM, hook, or consolidation pass. Export (B-02) reads WHERE shareable=1
|
|
2422
|
+
-- AND origin='local'. Defaults 0 so every pre-existing and machine-written row is
|
|
2423
|
+
-- un-shareable until a human says otherwise (fail-closed).
|
|
2424
|
+
shareable INTEGER NOT NULL DEFAULT 0,
|
|
2215
2425
|
created_at TEXT NOT NULL,
|
|
2216
2426
|
created_at_epoch INTEGER NOT NULL,
|
|
2217
2427
|
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
@@ -2485,7 +2695,13 @@ function initMemorySchema(db) {
|
|
|
2485
2695
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
2486
2696
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
2487
2697
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
2488
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2698
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2699
|
+
-- B-10 (Slice 5): cross-repo shared-memory observability. Without these seven,
|
|
2700
|
+
-- nobody could ever answer "what has crossed between my repos?" (and an ingest
|
|
2701
|
+
-- catch would swallow the CHECK violation, silently producing NOTHING).
|
|
2702
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2703
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2704
|
+
'shared_memory_revoked'
|
|
2489
2705
|
)),
|
|
2490
2706
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
2491
2707
|
model_id TEXT,
|
|
@@ -2854,6 +3070,7 @@ function initMemorySchema(db) {
|
|
|
2854
3070
|
`);
|
|
2855
3071
|
migrateObservationEmbeddingChunks(db);
|
|
2856
3072
|
migrateMemoryFilesFor4B(db);
|
|
3073
|
+
migrateSharedMemoryFor5B(db);
|
|
2857
3074
|
ensureRuleCandidatesTable(db);
|
|
2858
3075
|
migrateRuleDelivery(db);
|
|
2859
3076
|
}
|
|
@@ -3724,7 +3941,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
|
|
|
3724
3941
|
var init_memory_db = __esm({
|
|
3725
3942
|
"src/memory-db.ts"() {
|
|
3726
3943
|
"use strict";
|
|
3727
|
-
|
|
3944
|
+
init_db_driver();
|
|
3728
3945
|
init_config();
|
|
3729
3946
|
init_memory_vector();
|
|
3730
3947
|
init_db_backup();
|
|
@@ -3747,7 +3964,7 @@ var init_memory_db = __esm({
|
|
|
3747
3964
|
// src/hooks/incident-pipeline.ts
|
|
3748
3965
|
init_config();
|
|
3749
3966
|
init_memory_db();
|
|
3750
|
-
import { existsSync as
|
|
3967
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
|
|
3751
3968
|
import { basename as basename3, resolve as resolve5 } from "path";
|
|
3752
3969
|
|
|
3753
3970
|
// src/hooks/lib/write-hook-message.ts
|
|
@@ -3779,17 +3996,17 @@ async function main() {
|
|
|
3779
3996
|
process.exit(0);
|
|
3780
3997
|
return;
|
|
3781
3998
|
}
|
|
3782
|
-
if (!
|
|
3999
|
+
if (!existsSync9(filePath)) {
|
|
3783
4000
|
process.exit(0);
|
|
3784
4001
|
return;
|
|
3785
4002
|
}
|
|
3786
|
-
const content =
|
|
4003
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
3787
4004
|
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
3788
4005
|
const title = titleMatch?.[1] ?? basename3(filePath, ".md");
|
|
3789
4006
|
const slug = basename3(filePath, ".md").replace(/^\d{4}-\d{2}-\d{2}-?/, "").toLowerCase();
|
|
3790
4007
|
const memoryDirAbs = resolve5(root, memoryDir);
|
|
3791
4008
|
let hasExistingRule = false;
|
|
3792
|
-
if (
|
|
4009
|
+
if (existsSync9(memoryDirAbs)) {
|
|
3793
4010
|
const ruleFiles = readdirSync2(memoryDirAbs).filter((f) => f.startsWith("feedback_"));
|
|
3794
4011
|
for (const ruleFile of ruleFiles) {
|
|
3795
4012
|
if (ruleFile.toLowerCase().includes(slug.slice(0, 20))) {
|