@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
|
@@ -429,6 +429,136 @@ var init_sqlite_loader = __esm({
|
|
|
429
429
|
}
|
|
430
430
|
});
|
|
431
431
|
|
|
432
|
+
// src/db-driver.ts
|
|
433
|
+
import { createRequire as createRequire2 } from "module";
|
|
434
|
+
import { closeSync, existsSync as existsSync3, mkdtempSync, openSync, readFileSync as readFileSync3, rmSync as rmSync3, fsyncSync } from "fs";
|
|
435
|
+
import { tmpdir } from "os";
|
|
436
|
+
import { join as join2 } from "path";
|
|
437
|
+
function resolveDbEngine() {
|
|
438
|
+
return process.env[DB_ENGINE_ENV] === "better-sqlite3" ? "better-sqlite3" : DEFAULT_DB_ENGINE;
|
|
439
|
+
}
|
|
440
|
+
function nodeSqliteCtor() {
|
|
441
|
+
if (!_nodeCtor) _nodeCtor = req2("node:sqlite").DatabaseSync;
|
|
442
|
+
return _nodeCtor;
|
|
443
|
+
}
|
|
444
|
+
function plainRow(row) {
|
|
445
|
+
if (row == null || typeof row !== "object") return row;
|
|
446
|
+
return { ...row };
|
|
447
|
+
}
|
|
448
|
+
function openNodeSqlite(dbPath, opts) {
|
|
449
|
+
const Ctor = nodeSqliteCtor();
|
|
450
|
+
const raw = new Ctor(dbPath, {
|
|
451
|
+
open: true,
|
|
452
|
+
readOnly: !!opts.readonly,
|
|
453
|
+
// FAITHFULNESS: better-sqlite3's ACTUAL default is foreign_keys=ON (empirically
|
|
454
|
+
// verified — SQLite's own default is OFF, but bs3 enables it). massu was written +
|
|
455
|
+
// tested against that default, so the adapter must match it, or a store that relies
|
|
456
|
+
// on FK-ON-by-default (without an explicit pragma) would silently lose enforcement.
|
|
457
|
+
// Explicit `foreign_keys=OFF/ON` pragmas (e.g. memory-db bulk ops) still override.
|
|
458
|
+
enableForeignKeyConstraints: true,
|
|
459
|
+
allowExtension: false
|
|
460
|
+
});
|
|
461
|
+
let savepointSeq = 0;
|
|
462
|
+
const wrapStmt = (sql) => {
|
|
463
|
+
const st = raw.prepare(sql);
|
|
464
|
+
return {
|
|
465
|
+
run: (...p) => {
|
|
466
|
+
const r = st.run(...p);
|
|
467
|
+
return {
|
|
468
|
+
changes: typeof r.changes === "bigint" ? Number(r.changes) : r.changes,
|
|
469
|
+
lastInsertRowid: r.lastInsertRowid
|
|
470
|
+
};
|
|
471
|
+
},
|
|
472
|
+
get: (...p) => plainRow(st.get(...p)),
|
|
473
|
+
all: (...p) => st.all(...p).map(plainRow)
|
|
474
|
+
};
|
|
475
|
+
};
|
|
476
|
+
const pragma = (source, options) => {
|
|
477
|
+
const s = String(source).trim();
|
|
478
|
+
if (s.includes("=")) {
|
|
479
|
+
raw.exec(`PRAGMA ${s}`);
|
|
480
|
+
return void 0;
|
|
481
|
+
}
|
|
482
|
+
const rows = raw.prepare(`PRAGMA ${s}`).all().map(plainRow);
|
|
483
|
+
if (options?.simple) {
|
|
484
|
+
const first = rows[0];
|
|
485
|
+
return first ? Object.values(first)[0] : void 0;
|
|
486
|
+
}
|
|
487
|
+
return rows;
|
|
488
|
+
};
|
|
489
|
+
const transaction = (fn) => {
|
|
490
|
+
const run = (...args) => {
|
|
491
|
+
const nested = raw.isTransaction;
|
|
492
|
+
const name = `msp_${savepointSeq++}`;
|
|
493
|
+
if (nested) raw.exec(`SAVEPOINT ${name}`);
|
|
494
|
+
else raw.exec("BEGIN");
|
|
495
|
+
try {
|
|
496
|
+
const out = fn(...args);
|
|
497
|
+
if (nested) raw.exec(`RELEASE ${name}`);
|
|
498
|
+
else raw.exec("COMMIT");
|
|
499
|
+
return out;
|
|
500
|
+
} catch (e) {
|
|
501
|
+
if (nested) {
|
|
502
|
+
raw.exec(`ROLLBACK TO ${name}`);
|
|
503
|
+
raw.exec(`RELEASE ${name}`);
|
|
504
|
+
} else {
|
|
505
|
+
raw.exec("ROLLBACK");
|
|
506
|
+
}
|
|
507
|
+
throw e;
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
return run;
|
|
511
|
+
};
|
|
512
|
+
const serialize = () => {
|
|
513
|
+
if (raw.isTransaction) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
"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."
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
const dir = mkdtempSync(join2(tmpdir(), "massu-serialize-"));
|
|
519
|
+
const out = join2(dir, "snapshot.db");
|
|
520
|
+
try {
|
|
521
|
+
raw.exec(`VACUUM INTO '${out.replace(/'/g, "''")}'`);
|
|
522
|
+
const fd = openSync(out, "r");
|
|
523
|
+
try {
|
|
524
|
+
fsyncSync(fd);
|
|
525
|
+
} finally {
|
|
526
|
+
closeSync(fd);
|
|
527
|
+
}
|
|
528
|
+
return readFileSync3(out);
|
|
529
|
+
} finally {
|
|
530
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
const handle = {
|
|
534
|
+
prepare: (sql) => wrapStmt(sql),
|
|
535
|
+
exec: (sql) => raw.exec(sql),
|
|
536
|
+
pragma,
|
|
537
|
+
transaction,
|
|
538
|
+
serialize,
|
|
539
|
+
close: () => raw.close()
|
|
540
|
+
};
|
|
541
|
+
return handle;
|
|
542
|
+
}
|
|
543
|
+
function openBetterSqlite3(dbPath, opts) {
|
|
544
|
+
return openDatabase(dbPath, opts);
|
|
545
|
+
}
|
|
546
|
+
function openDatabase2(dbPath, opts = {}) {
|
|
547
|
+
return resolveDbEngine() === "better-sqlite3" ? openBetterSqlite3(dbPath, opts) : openNodeSqlite(dbPath, opts);
|
|
548
|
+
}
|
|
549
|
+
var req2, DEFAULT_DB_ENGINE, DB_ENGINE_ENV, _nodeCtor;
|
|
550
|
+
var init_db_driver = __esm({
|
|
551
|
+
"src/db-driver.ts"() {
|
|
552
|
+
"use strict";
|
|
553
|
+
init_sqlite_loader();
|
|
554
|
+
init_sqlite_loader();
|
|
555
|
+
req2 = createRequire2(import.meta.url);
|
|
556
|
+
DEFAULT_DB_ENGINE = "node-sqlite";
|
|
557
|
+
DB_ENGINE_ENV = "MASSU_DB_ENGINE";
|
|
558
|
+
_nodeCtor = null;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
432
562
|
// src/lib/memory-path.ts
|
|
433
563
|
function encodeMemoryDirName(projectRoot) {
|
|
434
564
|
return projectRoot.replace(/\//g, "-");
|
|
@@ -572,6 +702,32 @@ var init_config_memory_schema = __esm({
|
|
|
572
702
|
// turn of EVERY session, so an unbounded index is a permanent context tax — and the
|
|
573
703
|
// per-session cap bounds only the RATE, never the total.
|
|
574
704
|
indexMaxLines: z.number().int().min(1).default(50)
|
|
705
|
+
}).default({}),
|
|
706
|
+
// --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
|
|
707
|
+
// A decision made in one of your repos can surface in another — opt-in per
|
|
708
|
+
// decision AND opt-in per repo, signed, verified, and materialized ONLY on
|
|
709
|
+
// explicit human acceptance. Local transport is FREE and zero-network.
|
|
710
|
+
//
|
|
711
|
+
// ⛔ BOTH switches default OFF, and OFF means NOTHING EXISTS — no registry, no
|
|
712
|
+
// keys, no inbox, no behavioural difference. Two INDEPENDENT opt-ins:
|
|
713
|
+
// • enabled — may this repo EXPORT its shareable decisions? (opt-in #1)
|
|
714
|
+
// • subscribe — which repo LABELS may this repo IMPORT from? (opt-in #2)
|
|
715
|
+
// `subscribe: []` means import NOTHING. There is deliberately NO `subscribe: all`
|
|
716
|
+
// — a repo you never named is a repo Massu never reads from.
|
|
717
|
+
share: z.object({
|
|
718
|
+
enabled: z.boolean().default(false),
|
|
719
|
+
subscribe: z.array(z.string()).default([]),
|
|
720
|
+
// C-04 — recall surfacing of cross-repo memories. `enabled` defaults true but is
|
|
721
|
+
// CONDITIONAL on `subscribe` being non-empty (empty by default), so the effective
|
|
722
|
+
// default is DORMANT: with `subscribe: []` the recall hook output is byte-identical
|
|
723
|
+
// to today's. `maxCrossRepoItems` caps how many accepted cross-repo items may appear
|
|
724
|
+
// per recall block (default 1); `minScore` is an OPTIONAL strictly-higher floor for
|
|
725
|
+
// cross-repo items (they are, by construction, less relevant than local ones).
|
|
726
|
+
recall: z.object({
|
|
727
|
+
enabled: z.boolean().default(true),
|
|
728
|
+
maxCrossRepoItems: z.number().int().min(0).default(1),
|
|
729
|
+
minScore: z.number().optional()
|
|
730
|
+
}).default({})
|
|
575
731
|
}).default({})
|
|
576
732
|
}).optional();
|
|
577
733
|
}
|
|
@@ -579,7 +735,7 @@ var init_config_memory_schema = __esm({
|
|
|
579
735
|
|
|
580
736
|
// src/config.ts
|
|
581
737
|
import { resolve as resolve2, dirname as dirname3 } from "path";
|
|
582
|
-
import { existsSync as
|
|
738
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
583
739
|
import { homedir as homedir2 } from "os";
|
|
584
740
|
import { parse as parseYaml } from "yaml";
|
|
585
741
|
import { z as z2 } from "zod";
|
|
@@ -587,7 +743,7 @@ function findProjectRoot() {
|
|
|
587
743
|
const cwd = process.cwd();
|
|
588
744
|
let dir = cwd;
|
|
589
745
|
while (true) {
|
|
590
|
-
if (
|
|
746
|
+
if (existsSync4(resolve2(dir, "massu.config.yaml"))) {
|
|
591
747
|
return dir;
|
|
592
748
|
}
|
|
593
749
|
const parent = dirname3(dir);
|
|
@@ -596,10 +752,10 @@ function findProjectRoot() {
|
|
|
596
752
|
}
|
|
597
753
|
dir = cwd;
|
|
598
754
|
while (true) {
|
|
599
|
-
if (
|
|
755
|
+
if (existsSync4(resolve2(dir, "package.json"))) {
|
|
600
756
|
return dir;
|
|
601
757
|
}
|
|
602
|
-
if (
|
|
758
|
+
if (existsSync4(resolve2(dir, ".git"))) {
|
|
603
759
|
return dir;
|
|
604
760
|
}
|
|
605
761
|
const parent = dirname3(dir);
|
|
@@ -619,8 +775,8 @@ function getConfig() {
|
|
|
619
775
|
const root = getProjectRoot();
|
|
620
776
|
const configPath = resolve2(root, "massu.config.yaml");
|
|
621
777
|
let rawYaml = {};
|
|
622
|
-
if (
|
|
623
|
-
const content =
|
|
778
|
+
if (existsSync4(configPath)) {
|
|
779
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
624
780
|
rawYaml = parseYaml(content) ?? {};
|
|
625
781
|
}
|
|
626
782
|
const result = RawConfigSchema.safeParse(rawYaml);
|
|
@@ -748,6 +904,7 @@ function getResolvedPaths() {
|
|
|
748
904
|
sessionStatePath: resolve2(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
|
|
749
905
|
sessionArchivePath: resolve2(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
|
|
750
906
|
mcpJsonPath: resolve2(root, ".mcp.json"),
|
|
907
|
+
settingsPath: resolve2(root, claudeDirName, "settings.json"),
|
|
751
908
|
settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
|
|
752
909
|
};
|
|
753
910
|
}
|
|
@@ -825,7 +982,6 @@ var init_config = __esm({
|
|
|
825
982
|
custom_patterns: z2.array(CustomPatternSchema).default([])
|
|
826
983
|
}).optional(),
|
|
827
984
|
adr: z2.object({
|
|
828
|
-
detection_phrases: z2.array(z2.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
|
|
829
985
|
template: z2.string().default("default"),
|
|
830
986
|
storage: z2.string().default("database"),
|
|
831
987
|
output_dir: z2.string().default("docs/adr")
|
|
@@ -1179,26 +1335,26 @@ var init_memory_vector = __esm({
|
|
|
1179
1335
|
});
|
|
1180
1336
|
|
|
1181
1337
|
// src/db-backup.ts
|
|
1182
|
-
import { existsSync as
|
|
1183
|
-
import { resolve as resolve3, join as
|
|
1338
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1339
|
+
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
1184
1340
|
import { homedir as homedir3 } from "os";
|
|
1185
1341
|
function dbBackupsRoot(home = homedir3()) {
|
|
1186
1342
|
return resolve3(home, ".massu", "db-backups");
|
|
1187
1343
|
}
|
|
1188
1344
|
function projectBackupDir(projectRoot, home = homedir3()) {
|
|
1189
1345
|
const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
|
|
1190
|
-
return
|
|
1346
|
+
return join3(dbBackupsRoot(home), slug);
|
|
1191
1347
|
}
|
|
1192
1348
|
function backupStamp(nowMs) {
|
|
1193
1349
|
return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
|
|
1194
1350
|
}
|
|
1195
1351
|
function listDbBackups(projectRoot, home = homedir3()) {
|
|
1196
1352
|
const dir = projectBackupDir(projectRoot, home);
|
|
1197
|
-
if (!
|
|
1353
|
+
if (!existsSync5(dir)) return [];
|
|
1198
1354
|
const out = [];
|
|
1199
1355
|
for (const f of readdirSync(dir)) {
|
|
1200
1356
|
if (!f.endsWith(".db")) continue;
|
|
1201
|
-
const p =
|
|
1357
|
+
const p = join3(dir, f);
|
|
1202
1358
|
const st = statSync(p);
|
|
1203
1359
|
const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
|
|
1204
1360
|
out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
|
|
@@ -1211,13 +1367,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
|
|
|
1211
1367
|
if (backups.length === 0) return false;
|
|
1212
1368
|
const newest = backups[0];
|
|
1213
1369
|
if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
|
|
1214
|
-
if (
|
|
1370
|
+
if (existsSync5(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
|
|
1215
1371
|
return true;
|
|
1216
1372
|
}
|
|
1217
1373
|
function integrityOk(dbPath) {
|
|
1218
1374
|
let db = null;
|
|
1219
1375
|
try {
|
|
1220
|
-
db =
|
|
1376
|
+
db = openDatabase2(dbPath, { readonly: true });
|
|
1221
1377
|
const rows = db.pragma("integrity_check");
|
|
1222
1378
|
return rows.length === 1 && rows[0].integrity_check === "ok";
|
|
1223
1379
|
} catch {
|
|
@@ -1230,16 +1386,16 @@ function integrityOk(dbPath) {
|
|
|
1230
1386
|
}
|
|
1231
1387
|
}
|
|
1232
1388
|
function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
|
|
1233
|
-
if (!
|
|
1389
|
+
if (!existsSync5(dbPath)) {
|
|
1234
1390
|
throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
|
|
1235
1391
|
}
|
|
1236
1392
|
const dir = projectBackupDir(projectRoot, home);
|
|
1237
1393
|
mkdirSync4(dir, { recursive: true });
|
|
1238
1394
|
const name = basename(dbPath, ".db");
|
|
1239
|
-
const dest =
|
|
1395
|
+
const dest = join3(dir, `${name}-${backupStamp(nowMs)}.db`);
|
|
1240
1396
|
let src = null;
|
|
1241
1397
|
try {
|
|
1242
|
-
src =
|
|
1398
|
+
src = openDatabase2(dbPath, { readonly: true });
|
|
1243
1399
|
src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
1244
1400
|
} catch (err) {
|
|
1245
1401
|
throw new DbBackupError(
|
|
@@ -1278,7 +1434,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
|
|
|
1278
1434
|
}
|
|
1279
1435
|
function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
|
|
1280
1436
|
try {
|
|
1281
|
-
if (!
|
|
1437
|
+
if (!existsSync5(dbPath)) return null;
|
|
1282
1438
|
if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
|
|
1283
1439
|
return backupDb(projectRoot, dbPath, nowMs, home);
|
|
1284
1440
|
} catch (err) {
|
|
@@ -1290,7 +1446,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
|
|
|
1290
1446
|
var init_db_backup = __esm({
|
|
1291
1447
|
"src/db-backup.ts"() {
|
|
1292
1448
|
"use strict";
|
|
1293
|
-
|
|
1449
|
+
init_db_driver();
|
|
1294
1450
|
DEFAULT_RETENTION = 5;
|
|
1295
1451
|
FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
1296
1452
|
DbBackupError = class extends Error {
|
|
@@ -1403,21 +1559,21 @@ var init_rule_candidate_store = __esm({
|
|
|
1403
1559
|
});
|
|
1404
1560
|
|
|
1405
1561
|
// src/hooks/lib/hook-failure-signal.ts
|
|
1406
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as
|
|
1407
|
-
import { join as
|
|
1562
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
|
|
1563
|
+
import { join as join4, dirname as dirname5 } from "path";
|
|
1408
1564
|
function resolveFailureLogPath() {
|
|
1409
1565
|
const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
|
|
1410
1566
|
if (explicit) return explicit;
|
|
1411
1567
|
let dir = process.cwd();
|
|
1412
1568
|
for (let i = 0; i < 12; i++) {
|
|
1413
|
-
if (
|
|
1414
|
-
return
|
|
1569
|
+
if (existsSync6(join4(dir, ".massu")) || existsSync6(join4(dir, "massu.config.yaml"))) {
|
|
1570
|
+
return join4(dir, ".massu", "hook-failures.jsonl");
|
|
1415
1571
|
}
|
|
1416
1572
|
const parent = dirname5(dir);
|
|
1417
1573
|
if (parent === dir) break;
|
|
1418
1574
|
dir = parent;
|
|
1419
1575
|
}
|
|
1420
|
-
return
|
|
1576
|
+
return join4(process.cwd(), ".massu", "hook-failures.jsonl");
|
|
1421
1577
|
}
|
|
1422
1578
|
function recordHookFailure(hook, error, context) {
|
|
1423
1579
|
const record = {
|
|
@@ -1474,9 +1630,9 @@ var init_hook_failure_signal = __esm({
|
|
|
1474
1630
|
});
|
|
1475
1631
|
|
|
1476
1632
|
// src/memory-embedder-tokenizer.ts
|
|
1477
|
-
import { readFileSync as
|
|
1633
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1478
1634
|
function loadVocab(vocabPath) {
|
|
1479
|
-
const lines =
|
|
1635
|
+
const lines = readFileSync5(vocabPath, "utf-8").split("\n");
|
|
1480
1636
|
const vocab = /* @__PURE__ */ new Map();
|
|
1481
1637
|
for (let i = 0; i < lines.length; i++) {
|
|
1482
1638
|
const tok = lines[i].replace(/\r$/, "");
|
|
@@ -1596,9 +1752,9 @@ var init_memory_embedder_tokenizer = __esm({
|
|
|
1596
1752
|
|
|
1597
1753
|
// src/memory-embedder.ts
|
|
1598
1754
|
import { fileURLToPath } from "url";
|
|
1599
|
-
import { dirname as dirname6, join as
|
|
1600
|
-
import { existsSync as
|
|
1601
|
-
import { createRequire as
|
|
1755
|
+
import { dirname as dirname6, join as join5 } from "path";
|
|
1756
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1757
|
+
import { createRequire as createRequire3 } from "module";
|
|
1602
1758
|
function getActiveEmbedModel() {
|
|
1603
1759
|
return _activeModel;
|
|
1604
1760
|
}
|
|
@@ -1622,7 +1778,7 @@ function resolveModelDir() {
|
|
|
1622
1778
|
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
1623
1779
|
let root = null;
|
|
1624
1780
|
for (let i = 0; i < 8; i++) {
|
|
1625
|
-
if (
|
|
1781
|
+
if (existsSync7(join5(dir, "package.json"))) {
|
|
1626
1782
|
root = dir;
|
|
1627
1783
|
break;
|
|
1628
1784
|
}
|
|
@@ -1632,8 +1788,8 @@ function resolveModelDir() {
|
|
|
1632
1788
|
}
|
|
1633
1789
|
if (!root) return null;
|
|
1634
1790
|
for (const rel of ["dist/embedder", "assets/embedder"]) {
|
|
1635
|
-
const candidate =
|
|
1636
|
-
if (
|
|
1791
|
+
const candidate = join5(root, rel);
|
|
1792
|
+
if (existsSync7(join5(candidate, "model_quantized.onnx"))) return candidate;
|
|
1637
1793
|
}
|
|
1638
1794
|
return null;
|
|
1639
1795
|
} catch {
|
|
@@ -1642,8 +1798,8 @@ function resolveModelDir() {
|
|
|
1642
1798
|
}
|
|
1643
1799
|
function resolveWasmDir() {
|
|
1644
1800
|
try {
|
|
1645
|
-
const
|
|
1646
|
-
const main2 =
|
|
1801
|
+
const req3 = createRequire3(import.meta.url);
|
|
1802
|
+
const main2 = req3.resolve(ORT_PKG);
|
|
1647
1803
|
return dirname6(main2);
|
|
1648
1804
|
} catch {
|
|
1649
1805
|
return null;
|
|
@@ -1664,8 +1820,8 @@ async function getSession() {
|
|
|
1664
1820
|
ort.env.wasm.proxy = false;
|
|
1665
1821
|
ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
|
|
1666
1822
|
_ortTensor = ort.Tensor;
|
|
1667
|
-
const modelPath =
|
|
1668
|
-
const vocabPath =
|
|
1823
|
+
const modelPath = join5(modelDir, "model_quantized.onnx");
|
|
1824
|
+
const vocabPath = join5(modelDir, "vocab.txt");
|
|
1669
1825
|
_vocab = loadVocab(vocabPath);
|
|
1670
1826
|
const session = await ort.InferenceSession.create(modelPath, {
|
|
1671
1827
|
executionProviders: ["wasm"],
|
|
@@ -1986,6 +2142,7 @@ __export(memory_db_exports, {
|
|
|
1986
2142
|
migrateAuditLogCheckExtension: () => migrateAuditLogCheckExtension,
|
|
1987
2143
|
migrateMemoryFilesFor4B: () => migrateMemoryFilesFor4B,
|
|
1988
2144
|
migrateObservationEmbeddingChunks: () => migrateObservationEmbeddingChunks,
|
|
2145
|
+
migrateSharedMemoryFor5B: () => migrateSharedMemoryFor5B,
|
|
1989
2146
|
pruneOldConversationTurns: () => pruneOldConversationTurns,
|
|
1990
2147
|
pruneOldObservations: () => pruneOldObservations,
|
|
1991
2148
|
pruneToolCostEvents: () => pruneToolCostEvents,
|
|
@@ -2003,7 +2160,7 @@ __export(memory_db_exports, {
|
|
|
2003
2160
|
usageWarmupElapsed: () => usageWarmupElapsed
|
|
2004
2161
|
});
|
|
2005
2162
|
import { dirname as dirname7, basename as basename2 } from "path";
|
|
2006
|
-
import { existsSync as
|
|
2163
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
2007
2164
|
function sanitizeFts5Query(raw) {
|
|
2008
2165
|
const trimmed = raw.trim();
|
|
2009
2166
|
if (!trimmed) return '""';
|
|
@@ -2020,11 +2177,11 @@ function sanitizeFts5QueryOr(raw) {
|
|
|
2020
2177
|
function getMemoryDb() {
|
|
2021
2178
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
2022
2179
|
const dir = dirname7(dbPath);
|
|
2023
|
-
if (!
|
|
2180
|
+
if (!existsSync8(dir)) {
|
|
2024
2181
|
mkdirSync6(dir, { recursive: true });
|
|
2025
2182
|
}
|
|
2026
|
-
const preExisting =
|
|
2027
|
-
const db =
|
|
2183
|
+
const preExisting = existsSync8(dbPath);
|
|
2184
|
+
const db = openDatabase2(dbPath);
|
|
2028
2185
|
db.pragma("journal_mode = WAL");
|
|
2029
2186
|
db.pragma("foreign_keys = ON");
|
|
2030
2187
|
const onDisk = db.pragma("user_version", { simple: true });
|
|
@@ -2100,6 +2257,39 @@ function migrateMemoryFilesFor4B(db) {
|
|
|
2100
2257
|
db.exec(`ALTER TABLE ${add.table} ADD COLUMN ${add.name} ${add.decl}`);
|
|
2101
2258
|
}
|
|
2102
2259
|
}
|
|
2260
|
+
function migrateSharedMemoryFor5B(db) {
|
|
2261
|
+
const cols = db.prepare(`PRAGMA table_info(observations)`).all();
|
|
2262
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "shareable")) {
|
|
2263
|
+
db.exec(`ALTER TABLE observations ADD COLUMN shareable INTEGER NOT NULL DEFAULT 0`);
|
|
2264
|
+
}
|
|
2265
|
+
db.exec(`
|
|
2266
|
+
CREATE TABLE IF NOT EXISTS shared_memory_pending (
|
|
2267
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2268
|
+
record_hash TEXT NOT NULL UNIQUE, -- identity + idempotency key (hex sha256)
|
|
2269
|
+
origin_repo_id TEXT NOT NULL, -- the signed origin repo_id (v4 UUID)
|
|
2270
|
+
origin_repo_label TEXT NOT NULL, -- re-slugged on read; NEVER trusted from the wire
|
|
2271
|
+
envelope_raw TEXT NOT NULL, -- the VERBATIM signed bytes; B-05 re-verifies these
|
|
2272
|
+
record_json TEXT NOT NULL, -- the single record's canonical JSON
|
|
2273
|
+
received_at_epoch INTEGER NOT NULL, -- epoch SECONDS (Slice-2 convention)
|
|
2274
|
+
accepted_at_epoch INTEGER, -- set on accept (B-05); NULL while pending
|
|
2275
|
+
refused_at_epoch INTEGER, -- set on refuse (B-06)
|
|
2276
|
+
expired_at_epoch INTEGER -- revocation \u21D2 EXPIRE, never DELETE (B-07 / S-3)
|
|
2277
|
+
);
|
|
2278
|
+
CREATE INDEX IF NOT EXISTS idx_smp_origin ON shared_memory_pending(origin_repo_id);
|
|
2279
|
+
CREATE INDEX IF NOT EXISTS idx_smp_received ON shared_memory_pending(received_at_epoch DESC);
|
|
2280
|
+
`);
|
|
2281
|
+
db.exec(`
|
|
2282
|
+
CREATE TABLE IF NOT EXISTS shared_memory_outbound (
|
|
2283
|
+
record_hash TEXT PRIMARY KEY, -- the exported record's canonical hash
|
|
2284
|
+
observation_id INTEGER NOT NULL, -- the source observations.id (for B-07 revocation)
|
|
2285
|
+
origin_repo_id TEXT NOT NULL, -- this repo's own repo_id at export time
|
|
2286
|
+
seq INTEGER NOT NULL, -- the envelope seq this record first went out in
|
|
2287
|
+
exported_at_epoch INTEGER NOT NULL, -- epoch SECONDS
|
|
2288
|
+
revoked_at_epoch INTEGER -- set when the source row is superseded/expired (B-07)
|
|
2289
|
+
);
|
|
2290
|
+
CREATE INDEX IF NOT EXISTS idx_smo_obs ON shared_memory_outbound(observation_id);
|
|
2291
|
+
`);
|
|
2292
|
+
}
|
|
2103
2293
|
function migrateAuditLogCheckExtension(db) {
|
|
2104
2294
|
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
|
|
2105
2295
|
if (!row) return;
|
|
@@ -2122,7 +2312,16 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
2122
2312
|
"memory_file_adopted_human",
|
|
2123
2313
|
"memory_file_rendered",
|
|
2124
2314
|
"memory_file_render_refused",
|
|
2125
|
-
"memory_file_tombstoned"
|
|
2315
|
+
"memory_file_tombstoned",
|
|
2316
|
+
// B-10 (Slice 5) — the seven cross-repo shared-memory events. An existing DB whose
|
|
2317
|
+
// CHECK lacks any of these triggers the table rebuild below.
|
|
2318
|
+
"shared_memory_exported",
|
|
2319
|
+
"shared_memory_export_refused",
|
|
2320
|
+
"shared_memory_imported",
|
|
2321
|
+
"shared_memory_dropped",
|
|
2322
|
+
"shared_memory_accepted",
|
|
2323
|
+
"shared_memory_refused",
|
|
2324
|
+
"shared_memory_revoked"
|
|
2126
2325
|
];
|
|
2127
2326
|
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);
|
|
2128
2327
|
if (checkClauseMatch) {
|
|
@@ -2141,7 +2340,12 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
2141
2340
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
2142
2341
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
2143
2342
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
2144
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2343
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2344
|
+
-- B-10 (Slice 5) \u2014 cross-repo shared-memory events (must mirror the CREATE +
|
|
2345
|
+
-- the expected[] list above, or the migration would loop rebuilding forever).
|
|
2346
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2347
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2348
|
+
'shared_memory_revoked'
|
|
2145
2349
|
)),
|
|
2146
2350
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
2147
2351
|
model_id TEXT,
|
|
@@ -2218,6 +2422,12 @@ function initMemorySchema(db) {
|
|
|
2218
2422
|
-- credential, or takes a snapshot. A Slice-5 synced memory arrives here with
|
|
2219
2423
|
-- origin='team' and must never reach disk without CR-55's gates.
|
|
2220
2424
|
origin TEXT NOT NULL DEFAULT 'local',
|
|
2425
|
+
-- B-01 (Slice 5, cross-repo surfacing): the per-decision SHARE opt-in. Set to 1
|
|
2426
|
+
-- ONLY by an explicit human act (massu memory share <id>); NEVER by a heuristic,
|
|
2427
|
+
-- score, LLM, hook, or consolidation pass. Export (B-02) reads WHERE shareable=1
|
|
2428
|
+
-- AND origin='local'. Defaults 0 so every pre-existing and machine-written row is
|
|
2429
|
+
-- un-shareable until a human says otherwise (fail-closed).
|
|
2430
|
+
shareable INTEGER NOT NULL DEFAULT 0,
|
|
2221
2431
|
created_at TEXT NOT NULL,
|
|
2222
2432
|
created_at_epoch INTEGER NOT NULL,
|
|
2223
2433
|
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
@@ -2491,7 +2701,13 @@ function initMemorySchema(db) {
|
|
|
2491
2701
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
2492
2702
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
2493
2703
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
2494
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2704
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2705
|
+
-- B-10 (Slice 5): cross-repo shared-memory observability. Without these seven,
|
|
2706
|
+
-- nobody could ever answer "what has crossed between my repos?" (and an ingest
|
|
2707
|
+
-- catch would swallow the CHECK violation, silently producing NOTHING).
|
|
2708
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2709
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2710
|
+
'shared_memory_revoked'
|
|
2495
2711
|
)),
|
|
2496
2712
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
2497
2713
|
model_id TEXT,
|
|
@@ -2860,6 +3076,7 @@ function initMemorySchema(db) {
|
|
|
2860
3076
|
`);
|
|
2861
3077
|
migrateObservationEmbeddingChunks(db);
|
|
2862
3078
|
migrateMemoryFilesFor4B(db);
|
|
3079
|
+
migrateSharedMemoryFor5B(db);
|
|
2863
3080
|
ensureRuleCandidatesTable(db);
|
|
2864
3081
|
migrateRuleDelivery(db);
|
|
2865
3082
|
}
|
|
@@ -3730,7 +3947,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
|
|
|
3730
3947
|
var init_memory_db = __esm({
|
|
3731
3948
|
"src/memory-db.ts"() {
|
|
3732
3949
|
"use strict";
|
|
3733
|
-
|
|
3950
|
+
init_db_driver();
|
|
3734
3951
|
init_config();
|
|
3735
3952
|
init_memory_vector();
|
|
3736
3953
|
init_db_backup();
|
|
@@ -3760,68 +3977,6 @@ function estimateTokens(text) {
|
|
|
3760
3977
|
|
|
3761
3978
|
// src/observation-extractor.ts
|
|
3762
3979
|
init_memory_db();
|
|
3763
|
-
|
|
3764
|
-
// src/adr-generator.ts
|
|
3765
|
-
init_config();
|
|
3766
|
-
var DEFAULT_DETECTION_PHRASES = ["chose", "decided", "switching to", "moving from", "going with"];
|
|
3767
|
-
function getDetectionPhrases() {
|
|
3768
|
-
return getConfig().governance?.adr?.detection_phrases ?? DEFAULT_DETECTION_PHRASES;
|
|
3769
|
-
}
|
|
3770
|
-
function detectDecisionPatterns(text) {
|
|
3771
|
-
const phrases = getDetectionPhrases();
|
|
3772
|
-
const lower = text.toLowerCase();
|
|
3773
|
-
return phrases.some((phrase) => lower.includes(phrase));
|
|
3774
|
-
}
|
|
3775
|
-
function extractAlternatives(description) {
|
|
3776
|
-
const alternatives = [];
|
|
3777
|
-
const overMatch = description.match(/(\w[\w\s-]+)\s+over\s+(\w[\w\s-]+)/i);
|
|
3778
|
-
if (overMatch) {
|
|
3779
|
-
alternatives.push(overMatch[1].trim());
|
|
3780
|
-
alternatives.push(overMatch[2].trim());
|
|
3781
|
-
}
|
|
3782
|
-
const insteadMatch = description.match(/(\w[\w\s-]+)\s+instead\s+of\s+(\w[\w\s-]+)/i);
|
|
3783
|
-
if (insteadMatch) {
|
|
3784
|
-
alternatives.push(insteadMatch[1].trim());
|
|
3785
|
-
alternatives.push(insteadMatch[2].trim());
|
|
3786
|
-
}
|
|
3787
|
-
const switchMatch = description.match(/switching\s+from\s+(\w[\w\s-]+)\s+to\s+(\w[\w\s-]+)/i);
|
|
3788
|
-
if (switchMatch) {
|
|
3789
|
-
alternatives.push(switchMatch[2].trim());
|
|
3790
|
-
alternatives.push(switchMatch[1].trim());
|
|
3791
|
-
}
|
|
3792
|
-
return [...new Set(alternatives)];
|
|
3793
|
-
}
|
|
3794
|
-
function storeDecision(db, decision) {
|
|
3795
|
-
const existing = new Set(
|
|
3796
|
-
db.prepare(`PRAGMA table_info(architecture_decisions)`).all().map(
|
|
3797
|
-
(c) => c.name
|
|
3798
|
-
)
|
|
3799
|
-
);
|
|
3800
|
-
const cols = ["title", "context", "decision", "alternatives", "consequences", "session_id", "status"];
|
|
3801
|
-
const vals = [
|
|
3802
|
-
decision.title,
|
|
3803
|
-
decision.context,
|
|
3804
|
-
decision.decision,
|
|
3805
|
-
JSON.stringify(decision.alternatives),
|
|
3806
|
-
decision.consequences,
|
|
3807
|
-
decision.sessionId ?? null,
|
|
3808
|
-
decision.status ?? "accepted"
|
|
3809
|
-
];
|
|
3810
|
-
if (existing.has("affected_files")) {
|
|
3811
|
-
cols.push("affected_files");
|
|
3812
|
-
vals.push(decision.affectedFiles ? JSON.stringify(decision.affectedFiles) : null);
|
|
3813
|
-
}
|
|
3814
|
-
const hasTemporal = existing.has("valid_from_epoch");
|
|
3815
|
-
const extraSql = hasTemporal ? `, valid_from, ingested_at, valid_from_epoch, ingested_at_epoch` : "";
|
|
3816
|
-
const extraVals = hasTemporal ? `, datetime('now'), datetime('now'), CAST(strftime('%s','now') AS INTEGER), CAST(strftime('%s','now') AS INTEGER)` : "";
|
|
3817
|
-
const result = db.prepare(
|
|
3818
|
-
`INSERT INTO architecture_decisions (${cols.join(", ")}${extraSql})
|
|
3819
|
-
VALUES (${cols.map(() => "?").join(", ")}${extraVals})`
|
|
3820
|
-
).run(...vals);
|
|
3821
|
-
return Number(result.lastInsertRowid);
|
|
3822
|
-
}
|
|
3823
|
-
|
|
3824
|
-
// src/observation-extractor.ts
|
|
3825
3980
|
init_config();
|
|
3826
3981
|
import { homedir as homedir4 } from "os";
|
|
3827
3982
|
|
|
@@ -4124,23 +4279,6 @@ function classifyRealTimeToolCall(toolName, toolInput, toolResponse, seenReads2,
|
|
|
4124
4279
|
isError: isError ?? normalized.isError
|
|
4125
4280
|
};
|
|
4126
4281
|
if (isNoisyToolCall(tc, seenReads2)) return null;
|
|
4127
|
-
const responseText = normalized.text;
|
|
4128
|
-
if (responseText && detectDecisionPatterns(responseText)) {
|
|
4129
|
-
const firstLine = responseText.split("\n")[0].slice(0, 200);
|
|
4130
|
-
const title = `Architecture decision: ${firstLine}`;
|
|
4131
|
-
const detail = responseText.slice(0, 1e3);
|
|
4132
|
-
return {
|
|
4133
|
-
type: "decision",
|
|
4134
|
-
title,
|
|
4135
|
-
detail,
|
|
4136
|
-
visibility: classifyVisibility(title, detail),
|
|
4137
|
-
opts: {
|
|
4138
|
-
importance: assignImportance("decision"),
|
|
4139
|
-
originalTokens: estimateTokens(responseText),
|
|
4140
|
-
...extractLinkedReferences(responseText)
|
|
4141
|
-
}
|
|
4142
|
-
};
|
|
4143
|
-
}
|
|
4144
4282
|
return classifyToolCall(tc);
|
|
4145
4283
|
}
|
|
4146
4284
|
function detectPlanProgress(rawToolResponse) {
|
|
@@ -4257,8 +4395,8 @@ init_config();
|
|
|
4257
4395
|
|
|
4258
4396
|
// src/import-resolver.ts
|
|
4259
4397
|
init_config();
|
|
4260
|
-
import { readFileSync as
|
|
4261
|
-
import { resolve as resolve6, dirname as dirname8, join as
|
|
4398
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9, statSync as statSync2 } from "fs";
|
|
4399
|
+
import { resolve as resolve6, dirname as dirname8, join as join6 } from "path";
|
|
4262
4400
|
|
|
4263
4401
|
// src/security-utils.ts
|
|
4264
4402
|
import { resolve as resolve5, normalize } from "path";
|
|
@@ -4321,19 +4459,19 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
4321
4459
|
} else {
|
|
4322
4460
|
basePath = resolve6(dirname8(fromFile), specifier);
|
|
4323
4461
|
}
|
|
4324
|
-
if (
|
|
4462
|
+
if (existsSync9(basePath) && !isDirectory(basePath)) {
|
|
4325
4463
|
return toRelative(basePath);
|
|
4326
4464
|
}
|
|
4327
4465
|
const resolvedPaths = getResolvedPaths();
|
|
4328
4466
|
for (const ext of resolvedPaths.extensions) {
|
|
4329
4467
|
const withExt = basePath + ext;
|
|
4330
|
-
if (
|
|
4468
|
+
if (existsSync9(withExt)) {
|
|
4331
4469
|
return toRelative(withExt);
|
|
4332
4470
|
}
|
|
4333
4471
|
}
|
|
4334
4472
|
for (const indexFile of resolvedPaths.indexFiles) {
|
|
4335
|
-
const indexPath =
|
|
4336
|
-
if (
|
|
4473
|
+
const indexPath = join6(basePath, indexFile);
|
|
4474
|
+
if (existsSync9(indexPath)) {
|
|
4337
4475
|
return toRelative(indexPath);
|
|
4338
4476
|
}
|
|
4339
4477
|
}
|
|
@@ -4355,7 +4493,7 @@ function toRelative(absPath) {
|
|
|
4355
4493
|
}
|
|
4356
4494
|
|
|
4357
4495
|
// src/validation-engine.ts
|
|
4358
|
-
import { existsSync as
|
|
4496
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
4359
4497
|
function getValidationChecks() {
|
|
4360
4498
|
return getConfig().governance?.validation?.checks ?? {
|
|
4361
4499
|
rule_compliance: true,
|
|
@@ -4383,7 +4521,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
4383
4521
|
});
|
|
4384
4522
|
return checks;
|
|
4385
4523
|
}
|
|
4386
|
-
if (!
|
|
4524
|
+
if (!existsSync10(absPath)) {
|
|
4387
4525
|
checks.push({
|
|
4388
4526
|
name: "file_exists",
|
|
4389
4527
|
severity: "error",
|
|
@@ -4392,7 +4530,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
4392
4530
|
});
|
|
4393
4531
|
return checks;
|
|
4394
4532
|
}
|
|
4395
|
-
const source =
|
|
4533
|
+
const source = readFileSync7(absPath, "utf-8");
|
|
4396
4534
|
const lines = source.split("\n");
|
|
4397
4535
|
if (activeChecks.rule_compliance !== false) {
|
|
4398
4536
|
for (const ruleSet of config.rules) {
|
|
@@ -4491,7 +4629,7 @@ function storeValidationResult(db, filePath, checks, sessionId, validationType =
|
|
|
4491
4629
|
|
|
4492
4630
|
// src/security-scorer.ts
|
|
4493
4631
|
init_config();
|
|
4494
|
-
import { existsSync as
|
|
4632
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
4495
4633
|
var DEFAULT_SECURITY_PATTERNS = [
|
|
4496
4634
|
{
|
|
4497
4635
|
regex: /\bexec\s*\(\s*[`"'].*\$\{/,
|
|
@@ -4582,12 +4720,12 @@ function scoreFileSecurity(filePath, projectRoot) {
|
|
|
4582
4720
|
}]
|
|
4583
4721
|
};
|
|
4584
4722
|
}
|
|
4585
|
-
if (!
|
|
4723
|
+
if (!existsSync11(absPath)) {
|
|
4586
4724
|
return { riskScore: 0, findings: [] };
|
|
4587
4725
|
}
|
|
4588
4726
|
let source;
|
|
4589
4727
|
try {
|
|
4590
|
-
source =
|
|
4728
|
+
source = readFileSync8(absPath, "utf-8");
|
|
4591
4729
|
} catch {
|
|
4592
4730
|
return { riskScore: 0, findings: [] };
|
|
4593
4731
|
}
|
|
@@ -4627,8 +4765,8 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
|
|
|
4627
4765
|
|
|
4628
4766
|
// src/hooks/post-tool-use.ts
|
|
4629
4767
|
init_config();
|
|
4630
|
-
import { readFileSync as
|
|
4631
|
-
import { join as
|
|
4768
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync7, statSync as statSync3 } from "fs";
|
|
4769
|
+
import { join as join7, basename as pathBasename2, dirname as pathDirname, resolve as pathResolve } from "path";
|
|
4632
4770
|
|
|
4633
4771
|
// src/lib/recurrence-incrementer.ts
|
|
4634
4772
|
var ANSI = /\x1b\[[0-9;]*m/g;
|
|
@@ -4692,7 +4830,7 @@ function incrementRecurrenceCountsForScannerFailures(db, sessionId, scannerStdou
|
|
|
4692
4830
|
init_hook_failure_signal();
|
|
4693
4831
|
|
|
4694
4832
|
// src/memory-file-ingest.ts
|
|
4695
|
-
import { readFileSync as
|
|
4833
|
+
import { readFileSync as readFileSync9, existsSync as existsSync12, readdirSync as readdirSync2 } from "fs";
|
|
4696
4834
|
init_memory_db();
|
|
4697
4835
|
import { parse as parseYaml2 } from "yaml";
|
|
4698
4836
|
import { basename as pathBasename } from "path";
|
|
@@ -4801,8 +4939,8 @@ function upsertMemoryFileMirror(db, f) {
|
|
|
4801
4939
|
return "inserted";
|
|
4802
4940
|
}
|
|
4803
4941
|
function ingestMemoryFile(db, sessionId, filePath) {
|
|
4804
|
-
if (!
|
|
4805
|
-
const content =
|
|
4942
|
+
if (!existsSync12(filePath)) return "skipped";
|
|
4943
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
4806
4944
|
if (Buffer.byteLength(content, "utf-8") > MAX_MEMORY_FILE_BYTES) {
|
|
4807
4945
|
process.stderr.write(
|
|
4808
4946
|
`[massu] memory file exceeds ${MAX_MEMORY_FILE_BYTES} bytes, not ingested: ${filePath}
|
|
@@ -4919,27 +5057,6 @@ async function main() {
|
|
|
4919
5057
|
} else {
|
|
4920
5058
|
addObservation(db, session_id, observation.type, observation.title, observation.detail, observation.opts);
|
|
4921
5059
|
}
|
|
4922
|
-
if (observation.type === "decision") {
|
|
4923
|
-
try {
|
|
4924
|
-
const decisionText = observation.detail ?? observation.title;
|
|
4925
|
-
const alternatives = extractAlternatives(decisionText);
|
|
4926
|
-
const existing = db.prepare(
|
|
4927
|
-
`SELECT id FROM architecture_decisions WHERE session_id = ? AND title = ? LIMIT 1`
|
|
4928
|
-
).get(session_id, observation.title);
|
|
4929
|
-
if (!existing) {
|
|
4930
|
-
storeDecision(db, {
|
|
4931
|
-
title: observation.title,
|
|
4932
|
-
context: "",
|
|
4933
|
-
decision: decisionText,
|
|
4934
|
-
alternatives,
|
|
4935
|
-
consequences: "",
|
|
4936
|
-
sessionId: session_id,
|
|
4937
|
-
affectedFiles: observation.opts?.filesInvolved
|
|
4938
|
-
});
|
|
4939
|
-
}
|
|
4940
|
-
} catch {
|
|
4941
|
-
}
|
|
4942
|
-
}
|
|
4943
5060
|
if (tool_response) {
|
|
4944
5061
|
const progress = detectPlanProgress(tool_response);
|
|
4945
5062
|
if (progress.length > 0) {
|
|
@@ -5076,10 +5193,10 @@ async function main() {
|
|
|
5076
5193
|
} catch (err) {
|
|
5077
5194
|
try {
|
|
5078
5195
|
const projectRoot = process.cwd();
|
|
5079
|
-
const dir =
|
|
5080
|
-
if (!
|
|
5081
|
-
const logPath =
|
|
5082
|
-
const pre =
|
|
5196
|
+
const dir = join7(projectRoot, ".massu", "rule-candidates");
|
|
5197
|
+
if (!existsSync13(dir)) mkdirSync7(dir, { recursive: true });
|
|
5198
|
+
const logPath = join7(dir, ".cr53-increment-failures.jsonl");
|
|
5199
|
+
const pre = existsSync13(logPath) ? readFileSync10(logPath, "utf-8") : "";
|
|
5083
5200
|
const sep = pre && !pre.endsWith("\n") ? "\n" : "";
|
|
5084
5201
|
writeFileSync3(logPath, pre + sep + JSON.stringify({
|
|
5085
5202
|
session_id,
|
|
@@ -5184,13 +5301,13 @@ var _conventionDefaults = {
|
|
|
5184
5301
|
function readConventions(cwd) {
|
|
5185
5302
|
try {
|
|
5186
5303
|
const projectRoot = cwd ?? process.cwd();
|
|
5187
|
-
const configPath =
|
|
5188
|
-
if (!
|
|
5304
|
+
const configPath = join7(projectRoot, "massu.config.yaml");
|
|
5305
|
+
if (!existsSync13(configPath)) return _conventionDefaults;
|
|
5189
5306
|
const mtimeMs = statSync3(configPath).mtimeMs;
|
|
5190
5307
|
if (_cachedConventions !== null && _cachedConventionsPath === configPath && _cachedConventionsMtimeMs === mtimeMs) {
|
|
5191
5308
|
return _cachedConventions;
|
|
5192
5309
|
}
|
|
5193
|
-
const content =
|
|
5310
|
+
const content = readFileSync10(configPath, "utf-8");
|
|
5194
5311
|
const parsed = parseYaml3(content);
|
|
5195
5312
|
if (!parsed || typeof parsed !== "object") return _conventionDefaults;
|
|
5196
5313
|
const conventions = parsed.conventions;
|
|
@@ -5222,11 +5339,11 @@ function isKnowledgeSourceFile(filePath) {
|
|
|
5222
5339
|
function checkMemoryFileIntegrity(filePath) {
|
|
5223
5340
|
const issues = [];
|
|
5224
5341
|
try {
|
|
5225
|
-
if (!
|
|
5342
|
+
if (!existsSync13(filePath)) {
|
|
5226
5343
|
issues.push("MEMORY.md file does not exist after write");
|
|
5227
5344
|
return issues;
|
|
5228
5345
|
}
|
|
5229
|
-
const content =
|
|
5346
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
5230
5347
|
const lines = content.split("\n");
|
|
5231
5348
|
const MAX_LINES = 200;
|
|
5232
5349
|
if (lines.length > MAX_LINES) {
|