@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
|
@@ -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);
|
|
@@ -825,7 +981,6 @@ var init_config = __esm({
|
|
|
825
981
|
custom_patterns: z2.array(CustomPatternSchema).default([])
|
|
826
982
|
}).optional(),
|
|
827
983
|
adr: z2.object({
|
|
828
|
-
detection_phrases: z2.array(z2.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
|
|
829
984
|
template: z2.string().default("default"),
|
|
830
985
|
storage: z2.string().default("database"),
|
|
831
986
|
output_dir: z2.string().default("docs/adr")
|
|
@@ -1179,26 +1334,26 @@ var init_memory_vector = __esm({
|
|
|
1179
1334
|
});
|
|
1180
1335
|
|
|
1181
1336
|
// src/db-backup.ts
|
|
1182
|
-
import { existsSync as
|
|
1183
|
-
import { resolve as resolve3, join as
|
|
1337
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1338
|
+
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
1184
1339
|
import { homedir as homedir3 } from "os";
|
|
1185
1340
|
function dbBackupsRoot(home = homedir3()) {
|
|
1186
1341
|
return resolve3(home, ".massu", "db-backups");
|
|
1187
1342
|
}
|
|
1188
1343
|
function projectBackupDir(projectRoot, home = homedir3()) {
|
|
1189
1344
|
const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
|
|
1190
|
-
return
|
|
1345
|
+
return join3(dbBackupsRoot(home), slug);
|
|
1191
1346
|
}
|
|
1192
1347
|
function backupStamp(nowMs) {
|
|
1193
1348
|
return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
|
|
1194
1349
|
}
|
|
1195
1350
|
function listDbBackups(projectRoot, home = homedir3()) {
|
|
1196
1351
|
const dir = projectBackupDir(projectRoot, home);
|
|
1197
|
-
if (!
|
|
1352
|
+
if (!existsSync5(dir)) return [];
|
|
1198
1353
|
const out = [];
|
|
1199
1354
|
for (const f of readdirSync(dir)) {
|
|
1200
1355
|
if (!f.endsWith(".db")) continue;
|
|
1201
|
-
const p =
|
|
1356
|
+
const p = join3(dir, f);
|
|
1202
1357
|
const st = statSync(p);
|
|
1203
1358
|
const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
|
|
1204
1359
|
out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
|
|
@@ -1211,13 +1366,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
|
|
|
1211
1366
|
if (backups.length === 0) return false;
|
|
1212
1367
|
const newest = backups[0];
|
|
1213
1368
|
if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
|
|
1214
|
-
if (
|
|
1369
|
+
if (existsSync5(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
|
|
1215
1370
|
return true;
|
|
1216
1371
|
}
|
|
1217
1372
|
function integrityOk(dbPath) {
|
|
1218
1373
|
let db = null;
|
|
1219
1374
|
try {
|
|
1220
|
-
db =
|
|
1375
|
+
db = openDatabase2(dbPath, { readonly: true });
|
|
1221
1376
|
const rows = db.pragma("integrity_check");
|
|
1222
1377
|
return rows.length === 1 && rows[0].integrity_check === "ok";
|
|
1223
1378
|
} catch {
|
|
@@ -1230,16 +1385,16 @@ function integrityOk(dbPath) {
|
|
|
1230
1385
|
}
|
|
1231
1386
|
}
|
|
1232
1387
|
function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
|
|
1233
|
-
if (!
|
|
1388
|
+
if (!existsSync5(dbPath)) {
|
|
1234
1389
|
throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
|
|
1235
1390
|
}
|
|
1236
1391
|
const dir = projectBackupDir(projectRoot, home);
|
|
1237
1392
|
mkdirSync4(dir, { recursive: true });
|
|
1238
1393
|
const name = basename(dbPath, ".db");
|
|
1239
|
-
const dest =
|
|
1394
|
+
const dest = join3(dir, `${name}-${backupStamp(nowMs)}.db`);
|
|
1240
1395
|
let src = null;
|
|
1241
1396
|
try {
|
|
1242
|
-
src =
|
|
1397
|
+
src = openDatabase2(dbPath, { readonly: true });
|
|
1243
1398
|
src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
1244
1399
|
} catch (err) {
|
|
1245
1400
|
throw new DbBackupError(
|
|
@@ -1278,7 +1433,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
|
|
|
1278
1433
|
}
|
|
1279
1434
|
function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
|
|
1280
1435
|
try {
|
|
1281
|
-
if (!
|
|
1436
|
+
if (!existsSync5(dbPath)) return null;
|
|
1282
1437
|
if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
|
|
1283
1438
|
return backupDb(projectRoot, dbPath, nowMs, home);
|
|
1284
1439
|
} catch (err) {
|
|
@@ -1290,7 +1445,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
|
|
|
1290
1445
|
var init_db_backup = __esm({
|
|
1291
1446
|
"src/db-backup.ts"() {
|
|
1292
1447
|
"use strict";
|
|
1293
|
-
|
|
1448
|
+
init_db_driver();
|
|
1294
1449
|
DEFAULT_RETENTION = 5;
|
|
1295
1450
|
FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
1296
1451
|
DbBackupError = class extends Error {
|
|
@@ -1403,21 +1558,21 @@ var init_rule_candidate_store = __esm({
|
|
|
1403
1558
|
});
|
|
1404
1559
|
|
|
1405
1560
|
// src/hooks/lib/hook-failure-signal.ts
|
|
1406
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as
|
|
1407
|
-
import { join as
|
|
1561
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
|
|
1562
|
+
import { join as join4, dirname as dirname5 } from "path";
|
|
1408
1563
|
function resolveFailureLogPath() {
|
|
1409
1564
|
const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
|
|
1410
1565
|
if (explicit) return explicit;
|
|
1411
1566
|
let dir = process.cwd();
|
|
1412
1567
|
for (let i = 0; i < 12; i++) {
|
|
1413
|
-
if (
|
|
1414
|
-
return
|
|
1568
|
+
if (existsSync6(join4(dir, ".massu")) || existsSync6(join4(dir, "massu.config.yaml"))) {
|
|
1569
|
+
return join4(dir, ".massu", "hook-failures.jsonl");
|
|
1415
1570
|
}
|
|
1416
1571
|
const parent = dirname5(dir);
|
|
1417
1572
|
if (parent === dir) break;
|
|
1418
1573
|
dir = parent;
|
|
1419
1574
|
}
|
|
1420
|
-
return
|
|
1575
|
+
return join4(process.cwd(), ".massu", "hook-failures.jsonl");
|
|
1421
1576
|
}
|
|
1422
1577
|
function recordHookFailure(hook, error, context) {
|
|
1423
1578
|
const record = {
|
|
@@ -1474,9 +1629,9 @@ var init_hook_failure_signal = __esm({
|
|
|
1474
1629
|
});
|
|
1475
1630
|
|
|
1476
1631
|
// src/memory-embedder-tokenizer.ts
|
|
1477
|
-
import { readFileSync as
|
|
1632
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1478
1633
|
function loadVocab(vocabPath) {
|
|
1479
|
-
const lines =
|
|
1634
|
+
const lines = readFileSync5(vocabPath, "utf-8").split("\n");
|
|
1480
1635
|
const vocab = /* @__PURE__ */ new Map();
|
|
1481
1636
|
for (let i = 0; i < lines.length; i++) {
|
|
1482
1637
|
const tok = lines[i].replace(/\r$/, "");
|
|
@@ -1596,9 +1751,9 @@ var init_memory_embedder_tokenizer = __esm({
|
|
|
1596
1751
|
|
|
1597
1752
|
// src/memory-embedder.ts
|
|
1598
1753
|
import { fileURLToPath } from "url";
|
|
1599
|
-
import { dirname as dirname6, join as
|
|
1600
|
-
import { existsSync as
|
|
1601
|
-
import { createRequire as
|
|
1754
|
+
import { dirname as dirname6, join as join5 } from "path";
|
|
1755
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1756
|
+
import { createRequire as createRequire3 } from "module";
|
|
1602
1757
|
function getActiveEmbedModel() {
|
|
1603
1758
|
return _activeModel;
|
|
1604
1759
|
}
|
|
@@ -1622,7 +1777,7 @@ function resolveModelDir() {
|
|
|
1622
1777
|
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
1623
1778
|
let root = null;
|
|
1624
1779
|
for (let i = 0; i < 8; i++) {
|
|
1625
|
-
if (
|
|
1780
|
+
if (existsSync7(join5(dir, "package.json"))) {
|
|
1626
1781
|
root = dir;
|
|
1627
1782
|
break;
|
|
1628
1783
|
}
|
|
@@ -1632,8 +1787,8 @@ function resolveModelDir() {
|
|
|
1632
1787
|
}
|
|
1633
1788
|
if (!root) return null;
|
|
1634
1789
|
for (const rel of ["dist/embedder", "assets/embedder"]) {
|
|
1635
|
-
const candidate =
|
|
1636
|
-
if (
|
|
1790
|
+
const candidate = join5(root, rel);
|
|
1791
|
+
if (existsSync7(join5(candidate, "model_quantized.onnx"))) return candidate;
|
|
1637
1792
|
}
|
|
1638
1793
|
return null;
|
|
1639
1794
|
} catch {
|
|
@@ -1642,8 +1797,8 @@ function resolveModelDir() {
|
|
|
1642
1797
|
}
|
|
1643
1798
|
function resolveWasmDir() {
|
|
1644
1799
|
try {
|
|
1645
|
-
const
|
|
1646
|
-
const main2 =
|
|
1800
|
+
const req3 = createRequire3(import.meta.url);
|
|
1801
|
+
const main2 = req3.resolve(ORT_PKG);
|
|
1647
1802
|
return dirname6(main2);
|
|
1648
1803
|
} catch {
|
|
1649
1804
|
return null;
|
|
@@ -1664,8 +1819,8 @@ async function getSession() {
|
|
|
1664
1819
|
ort.env.wasm.proxy = false;
|
|
1665
1820
|
ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
|
|
1666
1821
|
_ortTensor = ort.Tensor;
|
|
1667
|
-
const modelPath =
|
|
1668
|
-
const vocabPath =
|
|
1822
|
+
const modelPath = join5(modelDir, "model_quantized.onnx");
|
|
1823
|
+
const vocabPath = join5(modelDir, "vocab.txt");
|
|
1669
1824
|
_vocab = loadVocab(vocabPath);
|
|
1670
1825
|
const session = await ort.InferenceSession.create(modelPath, {
|
|
1671
1826
|
executionProviders: ["wasm"],
|
|
@@ -1986,6 +2141,7 @@ __export(memory_db_exports, {
|
|
|
1986
2141
|
migrateAuditLogCheckExtension: () => migrateAuditLogCheckExtension,
|
|
1987
2142
|
migrateMemoryFilesFor4B: () => migrateMemoryFilesFor4B,
|
|
1988
2143
|
migrateObservationEmbeddingChunks: () => migrateObservationEmbeddingChunks,
|
|
2144
|
+
migrateSharedMemoryFor5B: () => migrateSharedMemoryFor5B,
|
|
1989
2145
|
pruneOldConversationTurns: () => pruneOldConversationTurns,
|
|
1990
2146
|
pruneOldObservations: () => pruneOldObservations,
|
|
1991
2147
|
pruneToolCostEvents: () => pruneToolCostEvents,
|
|
@@ -2003,7 +2159,7 @@ __export(memory_db_exports, {
|
|
|
2003
2159
|
usageWarmupElapsed: () => usageWarmupElapsed
|
|
2004
2160
|
});
|
|
2005
2161
|
import { dirname as dirname7, basename as basename2 } from "path";
|
|
2006
|
-
import { existsSync as
|
|
2162
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
2007
2163
|
function sanitizeFts5Query(raw) {
|
|
2008
2164
|
const trimmed = raw.trim();
|
|
2009
2165
|
if (!trimmed) return '""';
|
|
@@ -2020,11 +2176,11 @@ function sanitizeFts5QueryOr(raw) {
|
|
|
2020
2176
|
function getMemoryDb() {
|
|
2021
2177
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
2022
2178
|
const dir = dirname7(dbPath);
|
|
2023
|
-
if (!
|
|
2179
|
+
if (!existsSync8(dir)) {
|
|
2024
2180
|
mkdirSync6(dir, { recursive: true });
|
|
2025
2181
|
}
|
|
2026
|
-
const preExisting =
|
|
2027
|
-
const db =
|
|
2182
|
+
const preExisting = existsSync8(dbPath);
|
|
2183
|
+
const db = openDatabase2(dbPath);
|
|
2028
2184
|
db.pragma("journal_mode = WAL");
|
|
2029
2185
|
db.pragma("foreign_keys = ON");
|
|
2030
2186
|
const onDisk = db.pragma("user_version", { simple: true });
|
|
@@ -2100,6 +2256,39 @@ function migrateMemoryFilesFor4B(db) {
|
|
|
2100
2256
|
db.exec(`ALTER TABLE ${add.table} ADD COLUMN ${add.name} ${add.decl}`);
|
|
2101
2257
|
}
|
|
2102
2258
|
}
|
|
2259
|
+
function migrateSharedMemoryFor5B(db) {
|
|
2260
|
+
const cols = db.prepare(`PRAGMA table_info(observations)`).all();
|
|
2261
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "shareable")) {
|
|
2262
|
+
db.exec(`ALTER TABLE observations ADD COLUMN shareable INTEGER NOT NULL DEFAULT 0`);
|
|
2263
|
+
}
|
|
2264
|
+
db.exec(`
|
|
2265
|
+
CREATE TABLE IF NOT EXISTS shared_memory_pending (
|
|
2266
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2267
|
+
record_hash TEXT NOT NULL UNIQUE, -- identity + idempotency key (hex sha256)
|
|
2268
|
+
origin_repo_id TEXT NOT NULL, -- the signed origin repo_id (v4 UUID)
|
|
2269
|
+
origin_repo_label TEXT NOT NULL, -- re-slugged on read; NEVER trusted from the wire
|
|
2270
|
+
envelope_raw TEXT NOT NULL, -- the VERBATIM signed bytes; B-05 re-verifies these
|
|
2271
|
+
record_json TEXT NOT NULL, -- the single record's canonical JSON
|
|
2272
|
+
received_at_epoch INTEGER NOT NULL, -- epoch SECONDS (Slice-2 convention)
|
|
2273
|
+
accepted_at_epoch INTEGER, -- set on accept (B-05); NULL while pending
|
|
2274
|
+
refused_at_epoch INTEGER, -- set on refuse (B-06)
|
|
2275
|
+
expired_at_epoch INTEGER -- revocation \u21D2 EXPIRE, never DELETE (B-07 / S-3)
|
|
2276
|
+
);
|
|
2277
|
+
CREATE INDEX IF NOT EXISTS idx_smp_origin ON shared_memory_pending(origin_repo_id);
|
|
2278
|
+
CREATE INDEX IF NOT EXISTS idx_smp_received ON shared_memory_pending(received_at_epoch DESC);
|
|
2279
|
+
`);
|
|
2280
|
+
db.exec(`
|
|
2281
|
+
CREATE TABLE IF NOT EXISTS shared_memory_outbound (
|
|
2282
|
+
record_hash TEXT PRIMARY KEY, -- the exported record's canonical hash
|
|
2283
|
+
observation_id INTEGER NOT NULL, -- the source observations.id (for B-07 revocation)
|
|
2284
|
+
origin_repo_id TEXT NOT NULL, -- this repo's own repo_id at export time
|
|
2285
|
+
seq INTEGER NOT NULL, -- the envelope seq this record first went out in
|
|
2286
|
+
exported_at_epoch INTEGER NOT NULL, -- epoch SECONDS
|
|
2287
|
+
revoked_at_epoch INTEGER -- set when the source row is superseded/expired (B-07)
|
|
2288
|
+
);
|
|
2289
|
+
CREATE INDEX IF NOT EXISTS idx_smo_obs ON shared_memory_outbound(observation_id);
|
|
2290
|
+
`);
|
|
2291
|
+
}
|
|
2103
2292
|
function migrateAuditLogCheckExtension(db) {
|
|
2104
2293
|
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
|
|
2105
2294
|
if (!row) return;
|
|
@@ -2122,7 +2311,16 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
2122
2311
|
"memory_file_adopted_human",
|
|
2123
2312
|
"memory_file_rendered",
|
|
2124
2313
|
"memory_file_render_refused",
|
|
2125
|
-
"memory_file_tombstoned"
|
|
2314
|
+
"memory_file_tombstoned",
|
|
2315
|
+
// B-10 (Slice 5) — the seven cross-repo shared-memory events. An existing DB whose
|
|
2316
|
+
// CHECK lacks any of these triggers the table rebuild below.
|
|
2317
|
+
"shared_memory_exported",
|
|
2318
|
+
"shared_memory_export_refused",
|
|
2319
|
+
"shared_memory_imported",
|
|
2320
|
+
"shared_memory_dropped",
|
|
2321
|
+
"shared_memory_accepted",
|
|
2322
|
+
"shared_memory_refused",
|
|
2323
|
+
"shared_memory_revoked"
|
|
2126
2324
|
];
|
|
2127
2325
|
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
2326
|
if (checkClauseMatch) {
|
|
@@ -2141,7 +2339,12 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
2141
2339
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
2142
2340
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
2143
2341
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
2144
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2342
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2343
|
+
-- B-10 (Slice 5) \u2014 cross-repo shared-memory events (must mirror the CREATE +
|
|
2344
|
+
-- the expected[] list above, or the migration would loop rebuilding forever).
|
|
2345
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2346
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2347
|
+
'shared_memory_revoked'
|
|
2145
2348
|
)),
|
|
2146
2349
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
2147
2350
|
model_id TEXT,
|
|
@@ -2218,6 +2421,12 @@ function initMemorySchema(db) {
|
|
|
2218
2421
|
-- credential, or takes a snapshot. A Slice-5 synced memory arrives here with
|
|
2219
2422
|
-- origin='team' and must never reach disk without CR-55's gates.
|
|
2220
2423
|
origin TEXT NOT NULL DEFAULT 'local',
|
|
2424
|
+
-- B-01 (Slice 5, cross-repo surfacing): the per-decision SHARE opt-in. Set to 1
|
|
2425
|
+
-- ONLY by an explicit human act (massu memory share <id>); NEVER by a heuristic,
|
|
2426
|
+
-- score, LLM, hook, or consolidation pass. Export (B-02) reads WHERE shareable=1
|
|
2427
|
+
-- AND origin='local'. Defaults 0 so every pre-existing and machine-written row is
|
|
2428
|
+
-- un-shareable until a human says otherwise (fail-closed).
|
|
2429
|
+
shareable INTEGER NOT NULL DEFAULT 0,
|
|
2221
2430
|
created_at TEXT NOT NULL,
|
|
2222
2431
|
created_at_epoch INTEGER NOT NULL,
|
|
2223
2432
|
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
@@ -2491,7 +2700,13 @@ function initMemorySchema(db) {
|
|
|
2491
2700
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
2492
2701
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
2493
2702
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
2494
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2703
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2704
|
+
-- B-10 (Slice 5): cross-repo shared-memory observability. Without these seven,
|
|
2705
|
+
-- nobody could ever answer "what has crossed between my repos?" (and an ingest
|
|
2706
|
+
-- catch would swallow the CHECK violation, silently producing NOTHING).
|
|
2707
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2708
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2709
|
+
'shared_memory_revoked'
|
|
2495
2710
|
)),
|
|
2496
2711
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
2497
2712
|
model_id TEXT,
|
|
@@ -2860,6 +3075,7 @@ function initMemorySchema(db) {
|
|
|
2860
3075
|
`);
|
|
2861
3076
|
migrateObservationEmbeddingChunks(db);
|
|
2862
3077
|
migrateMemoryFilesFor4B(db);
|
|
3078
|
+
migrateSharedMemoryFor5B(db);
|
|
2863
3079
|
ensureRuleCandidatesTable(db);
|
|
2864
3080
|
migrateRuleDelivery(db);
|
|
2865
3081
|
}
|
|
@@ -3730,7 +3946,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
|
|
|
3730
3946
|
var init_memory_db = __esm({
|
|
3731
3947
|
"src/memory-db.ts"() {
|
|
3732
3948
|
"use strict";
|
|
3733
|
-
|
|
3949
|
+
init_db_driver();
|
|
3734
3950
|
init_config();
|
|
3735
3951
|
init_memory_vector();
|
|
3736
3952
|
init_db_backup();
|
|
@@ -3760,68 +3976,6 @@ function estimateTokens(text) {
|
|
|
3760
3976
|
|
|
3761
3977
|
// src/observation-extractor.ts
|
|
3762
3978
|
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
3979
|
init_config();
|
|
3826
3980
|
import { homedir as homedir4 } from "os";
|
|
3827
3981
|
|
|
@@ -4124,23 +4278,6 @@ function classifyRealTimeToolCall(toolName, toolInput, toolResponse, seenReads2,
|
|
|
4124
4278
|
isError: isError ?? normalized.isError
|
|
4125
4279
|
};
|
|
4126
4280
|
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
4281
|
return classifyToolCall(tc);
|
|
4145
4282
|
}
|
|
4146
4283
|
function detectPlanProgress(rawToolResponse) {
|
|
@@ -4257,8 +4394,8 @@ init_config();
|
|
|
4257
4394
|
|
|
4258
4395
|
// src/import-resolver.ts
|
|
4259
4396
|
init_config();
|
|
4260
|
-
import { readFileSync as
|
|
4261
|
-
import { resolve as resolve6, dirname as dirname8, join as
|
|
4397
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9, statSync as statSync2 } from "fs";
|
|
4398
|
+
import { resolve as resolve6, dirname as dirname8, join as join6 } from "path";
|
|
4262
4399
|
|
|
4263
4400
|
// src/security-utils.ts
|
|
4264
4401
|
import { resolve as resolve5, normalize } from "path";
|
|
@@ -4321,19 +4458,19 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
4321
4458
|
} else {
|
|
4322
4459
|
basePath = resolve6(dirname8(fromFile), specifier);
|
|
4323
4460
|
}
|
|
4324
|
-
if (
|
|
4461
|
+
if (existsSync9(basePath) && !isDirectory(basePath)) {
|
|
4325
4462
|
return toRelative(basePath);
|
|
4326
4463
|
}
|
|
4327
4464
|
const resolvedPaths = getResolvedPaths();
|
|
4328
4465
|
for (const ext of resolvedPaths.extensions) {
|
|
4329
4466
|
const withExt = basePath + ext;
|
|
4330
|
-
if (
|
|
4467
|
+
if (existsSync9(withExt)) {
|
|
4331
4468
|
return toRelative(withExt);
|
|
4332
4469
|
}
|
|
4333
4470
|
}
|
|
4334
4471
|
for (const indexFile of resolvedPaths.indexFiles) {
|
|
4335
|
-
const indexPath =
|
|
4336
|
-
if (
|
|
4472
|
+
const indexPath = join6(basePath, indexFile);
|
|
4473
|
+
if (existsSync9(indexPath)) {
|
|
4337
4474
|
return toRelative(indexPath);
|
|
4338
4475
|
}
|
|
4339
4476
|
}
|
|
@@ -4355,7 +4492,7 @@ function toRelative(absPath) {
|
|
|
4355
4492
|
}
|
|
4356
4493
|
|
|
4357
4494
|
// src/validation-engine.ts
|
|
4358
|
-
import { existsSync as
|
|
4495
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
4359
4496
|
function getValidationChecks() {
|
|
4360
4497
|
return getConfig().governance?.validation?.checks ?? {
|
|
4361
4498
|
rule_compliance: true,
|
|
@@ -4383,7 +4520,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
4383
4520
|
});
|
|
4384
4521
|
return checks;
|
|
4385
4522
|
}
|
|
4386
|
-
if (!
|
|
4523
|
+
if (!existsSync10(absPath)) {
|
|
4387
4524
|
checks.push({
|
|
4388
4525
|
name: "file_exists",
|
|
4389
4526
|
severity: "error",
|
|
@@ -4392,7 +4529,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
4392
4529
|
});
|
|
4393
4530
|
return checks;
|
|
4394
4531
|
}
|
|
4395
|
-
const source =
|
|
4532
|
+
const source = readFileSync7(absPath, "utf-8");
|
|
4396
4533
|
const lines = source.split("\n");
|
|
4397
4534
|
if (activeChecks.rule_compliance !== false) {
|
|
4398
4535
|
for (const ruleSet of config.rules) {
|
|
@@ -4491,7 +4628,7 @@ function storeValidationResult(db, filePath, checks, sessionId, validationType =
|
|
|
4491
4628
|
|
|
4492
4629
|
// src/security-scorer.ts
|
|
4493
4630
|
init_config();
|
|
4494
|
-
import { existsSync as
|
|
4631
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
4495
4632
|
var DEFAULT_SECURITY_PATTERNS = [
|
|
4496
4633
|
{
|
|
4497
4634
|
regex: /\bexec\s*\(\s*[`"'].*\$\{/,
|
|
@@ -4582,12 +4719,12 @@ function scoreFileSecurity(filePath, projectRoot) {
|
|
|
4582
4719
|
}]
|
|
4583
4720
|
};
|
|
4584
4721
|
}
|
|
4585
|
-
if (!
|
|
4722
|
+
if (!existsSync11(absPath)) {
|
|
4586
4723
|
return { riskScore: 0, findings: [] };
|
|
4587
4724
|
}
|
|
4588
4725
|
let source;
|
|
4589
4726
|
try {
|
|
4590
|
-
source =
|
|
4727
|
+
source = readFileSync8(absPath, "utf-8");
|
|
4591
4728
|
} catch {
|
|
4592
4729
|
return { riskScore: 0, findings: [] };
|
|
4593
4730
|
}
|
|
@@ -4627,8 +4764,8 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
|
|
|
4627
4764
|
|
|
4628
4765
|
// src/hooks/post-tool-use.ts
|
|
4629
4766
|
init_config();
|
|
4630
|
-
import { readFileSync as
|
|
4631
|
-
import { join as
|
|
4767
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync7, statSync as statSync3 } from "fs";
|
|
4768
|
+
import { join as join7, basename as pathBasename2, dirname as pathDirname, resolve as pathResolve } from "path";
|
|
4632
4769
|
|
|
4633
4770
|
// src/lib/recurrence-incrementer.ts
|
|
4634
4771
|
var ANSI = /\x1b\[[0-9;]*m/g;
|
|
@@ -4692,7 +4829,7 @@ function incrementRecurrenceCountsForScannerFailures(db, sessionId, scannerStdou
|
|
|
4692
4829
|
init_hook_failure_signal();
|
|
4693
4830
|
|
|
4694
4831
|
// src/memory-file-ingest.ts
|
|
4695
|
-
import { readFileSync as
|
|
4832
|
+
import { readFileSync as readFileSync9, existsSync as existsSync12, readdirSync as readdirSync2 } from "fs";
|
|
4696
4833
|
init_memory_db();
|
|
4697
4834
|
import { parse as parseYaml2 } from "yaml";
|
|
4698
4835
|
import { basename as pathBasename } from "path";
|
|
@@ -4801,8 +4938,8 @@ function upsertMemoryFileMirror(db, f) {
|
|
|
4801
4938
|
return "inserted";
|
|
4802
4939
|
}
|
|
4803
4940
|
function ingestMemoryFile(db, sessionId, filePath) {
|
|
4804
|
-
if (!
|
|
4805
|
-
const content =
|
|
4941
|
+
if (!existsSync12(filePath)) return "skipped";
|
|
4942
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
4806
4943
|
if (Buffer.byteLength(content, "utf-8") > MAX_MEMORY_FILE_BYTES) {
|
|
4807
4944
|
process.stderr.write(
|
|
4808
4945
|
`[massu] memory file exceeds ${MAX_MEMORY_FILE_BYTES} bytes, not ingested: ${filePath}
|
|
@@ -4919,27 +5056,6 @@ async function main() {
|
|
|
4919
5056
|
} else {
|
|
4920
5057
|
addObservation(db, session_id, observation.type, observation.title, observation.detail, observation.opts);
|
|
4921
5058
|
}
|
|
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
5059
|
if (tool_response) {
|
|
4944
5060
|
const progress = detectPlanProgress(tool_response);
|
|
4945
5061
|
if (progress.length > 0) {
|
|
@@ -5076,10 +5192,10 @@ async function main() {
|
|
|
5076
5192
|
} catch (err) {
|
|
5077
5193
|
try {
|
|
5078
5194
|
const projectRoot = process.cwd();
|
|
5079
|
-
const dir =
|
|
5080
|
-
if (!
|
|
5081
|
-
const logPath =
|
|
5082
|
-
const pre =
|
|
5195
|
+
const dir = join7(projectRoot, ".massu", "rule-candidates");
|
|
5196
|
+
if (!existsSync13(dir)) mkdirSync7(dir, { recursive: true });
|
|
5197
|
+
const logPath = join7(dir, ".cr53-increment-failures.jsonl");
|
|
5198
|
+
const pre = existsSync13(logPath) ? readFileSync10(logPath, "utf-8") : "";
|
|
5083
5199
|
const sep = pre && !pre.endsWith("\n") ? "\n" : "";
|
|
5084
5200
|
writeFileSync3(logPath, pre + sep + JSON.stringify({
|
|
5085
5201
|
session_id,
|
|
@@ -5184,13 +5300,13 @@ var _conventionDefaults = {
|
|
|
5184
5300
|
function readConventions(cwd) {
|
|
5185
5301
|
try {
|
|
5186
5302
|
const projectRoot = cwd ?? process.cwd();
|
|
5187
|
-
const configPath =
|
|
5188
|
-
if (!
|
|
5303
|
+
const configPath = join7(projectRoot, "massu.config.yaml");
|
|
5304
|
+
if (!existsSync13(configPath)) return _conventionDefaults;
|
|
5189
5305
|
const mtimeMs = statSync3(configPath).mtimeMs;
|
|
5190
5306
|
if (_cachedConventions !== null && _cachedConventionsPath === configPath && _cachedConventionsMtimeMs === mtimeMs) {
|
|
5191
5307
|
return _cachedConventions;
|
|
5192
5308
|
}
|
|
5193
|
-
const content =
|
|
5309
|
+
const content = readFileSync10(configPath, "utf-8");
|
|
5194
5310
|
const parsed = parseYaml3(content);
|
|
5195
5311
|
if (!parsed || typeof parsed !== "object") return _conventionDefaults;
|
|
5196
5312
|
const conventions = parsed.conventions;
|
|
@@ -5222,11 +5338,11 @@ function isKnowledgeSourceFile(filePath) {
|
|
|
5222
5338
|
function checkMemoryFileIntegrity(filePath) {
|
|
5223
5339
|
const issues = [];
|
|
5224
5340
|
try {
|
|
5225
|
-
if (!
|
|
5341
|
+
if (!existsSync13(filePath)) {
|
|
5226
5342
|
issues.push("MEMORY.md file does not exist after write");
|
|
5227
5343
|
return issues;
|
|
5228
5344
|
}
|
|
5229
|
-
const content =
|
|
5345
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
5230
5346
|
const lines = content.split("\n");
|
|
5231
5347
|
const MAX_LINES = 200;
|
|
5232
5348
|
if (lines.length > MAX_LINES) {
|