@massu/core 2.5.5 → 2.5.6
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 +33 -0
- package/dist/cli.js +69 -42
- package/dist/hooks/classify-failure.js +27 -654
- package/dist/hooks/fix-detector.js +4 -2
- package/dist/hooks/pre-delete-check.js +379 -69
- package/dist/hooks/pre-tool-use-gate.js +170 -221
- package/dist/hooks/security-gate.js +544 -179
- package/dist/hooks/session-end.js +470 -63
- package/package.json +1 -1
|
@@ -1648,9 +1648,9 @@ function nackLearning(db, lease, error) {
|
|
|
1648
1648
|
return snap;
|
|
1649
1649
|
}
|
|
1650
1650
|
function undeliveredSnapshot(db) {
|
|
1651
|
-
const count = (
|
|
1652
|
-
if (!tableExists(db,
|
|
1653
|
-
const r2 = db.prepare(`SELECT COUNT(*) AS n FROM ${
|
|
1651
|
+
const count = (t2) => {
|
|
1652
|
+
if (!tableExists(db, t2)) return 0;
|
|
1653
|
+
const r2 = db.prepare(`SELECT COUNT(*) AS n FROM ${t2}`).get();
|
|
1654
1654
|
return r2.n;
|
|
1655
1655
|
};
|
|
1656
1656
|
const promotions = count("team_promotion_outbound");
|
|
@@ -1658,10 +1658,10 @@ function undeliveredSnapshot(db) {
|
|
|
1658
1658
|
const events = count(TELEMETRY_OUTBOUND_TABLE);
|
|
1659
1659
|
let maxAttempts = 0;
|
|
1660
1660
|
let oldest = null;
|
|
1661
|
-
for (const
|
|
1662
|
-
if (!tableExists(db,
|
|
1663
|
-
if (!columns(db,
|
|
1664
|
-
const r2 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${
|
|
1661
|
+
for (const t2 of RULE_OUTBOUND_TABLES) {
|
|
1662
|
+
if (!tableExists(db, t2)) continue;
|
|
1663
|
+
if (!columns(db, t2).has("attempts")) continue;
|
|
1664
|
+
const r2 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${t2}`).get();
|
|
1665
1665
|
if (r2.a !== null && r2.a > maxAttempts) maxAttempts = r2.a;
|
|
1666
1666
|
if (r2.c !== null && (oldest === null || r2.c < oldest)) oldest = r2.c;
|
|
1667
1667
|
}
|
|
@@ -1958,13 +1958,13 @@ function encode(text, vocab, opts = {}) {
|
|
|
1958
1958
|
const maxLen = opts.maxLen ?? 256;
|
|
1959
1959
|
const basic = basicTokenize(text);
|
|
1960
1960
|
const wpTokens = [];
|
|
1961
|
-
for (const
|
|
1962
|
-
for (const sub of wordpieceTokenize(
|
|
1961
|
+
for (const t2 of basic) {
|
|
1962
|
+
for (const sub of wordpieceTokenize(t2, vocab)) wpTokens.push(sub);
|
|
1963
1963
|
}
|
|
1964
1964
|
const truncated = wpTokens.slice(0, maxLen - 2);
|
|
1965
1965
|
const tokens = [CLS, ...truncated, SEP];
|
|
1966
1966
|
const unkId = vocab.get(UNK) ?? 100;
|
|
1967
|
-
const input_ids = tokens.map((
|
|
1967
|
+
const input_ids = tokens.map((t2) => vocab.get(t2) ?? unkId);
|
|
1968
1968
|
const attention_mask = tokens.map(() => 1);
|
|
1969
1969
|
const token_type_ids = tokens.map(() => 0);
|
|
1970
1970
|
return { tokens, input_ids, attention_mask, token_type_ids };
|
|
@@ -2066,11 +2066,11 @@ async function getSession() {
|
|
|
2066
2066
|
function meanPool(hidden, mask, seqLen, hiddenSize) {
|
|
2067
2067
|
const out = new Float32Array(hiddenSize);
|
|
2068
2068
|
let maskSum = 0;
|
|
2069
|
-
for (let
|
|
2070
|
-
const m = mask[
|
|
2069
|
+
for (let t2 = 0; t2 < seqLen; t2++) {
|
|
2070
|
+
const m = mask[t2];
|
|
2071
2071
|
if (m === 0) continue;
|
|
2072
2072
|
maskSum += m;
|
|
2073
|
-
const base =
|
|
2073
|
+
const base = t2 * hiddenSize;
|
|
2074
2074
|
for (let h = 0; h < hiddenSize; h++) out[h] += hidden[base + h] * m;
|
|
2075
2075
|
}
|
|
2076
2076
|
const denom = Math.max(maskSum, 1e-9);
|
|
@@ -2078,16 +2078,16 @@ function meanPool(hidden, mask, seqLen, hiddenSize) {
|
|
|
2078
2078
|
return out;
|
|
2079
2079
|
}
|
|
2080
2080
|
function chunkForEmbedding(text) {
|
|
2081
|
-
const
|
|
2082
|
-
if (!
|
|
2083
|
-
if (
|
|
2081
|
+
const t2 = text.trim();
|
|
2082
|
+
if (!t2) return [];
|
|
2083
|
+
if (t2.length <= CHUNK_TARGET_CHARS) return [t2];
|
|
2084
2084
|
const chunks = [];
|
|
2085
2085
|
let start = 0;
|
|
2086
|
-
while (start <
|
|
2087
|
-
let end = Math.min(start + CHUNK_TARGET_CHARS,
|
|
2088
|
-
if (end <
|
|
2086
|
+
while (start < t2.length && chunks.length < MAX_CHUNKS_PER_RECORD) {
|
|
2087
|
+
let end = Math.min(start + CHUNK_TARGET_CHARS, t2.length);
|
|
2088
|
+
if (end < t2.length) {
|
|
2089
2089
|
const floor = start + Math.floor(CHUNK_TARGET_CHARS / 2);
|
|
2090
|
-
const slice =
|
|
2090
|
+
const slice = t2.slice(start, end);
|
|
2091
2091
|
const para = slice.lastIndexOf("\n\n");
|
|
2092
2092
|
const sent = Math.max(slice.lastIndexOf(". "), slice.lastIndexOf(".\n"));
|
|
2093
2093
|
const space = slice.lastIndexOf(" ");
|
|
@@ -2099,9 +2099,9 @@ function chunkForEmbedding(text) {
|
|
|
2099
2099
|
}
|
|
2100
2100
|
}
|
|
2101
2101
|
}
|
|
2102
|
-
const chunk =
|
|
2102
|
+
const chunk = t2.slice(start, end).trim();
|
|
2103
2103
|
if (chunk) chunks.push(chunk);
|
|
2104
|
-
if (end >=
|
|
2104
|
+
if (end >= t2.length) break;
|
|
2105
2105
|
start = Math.max(end - CHUNK_OVERLAP_CHARS, start + 1);
|
|
2106
2106
|
}
|
|
2107
2107
|
return chunks;
|
|
@@ -2186,17 +2186,17 @@ async function embedBatch(texts) {
|
|
|
2186
2186
|
const settings = loadEmbedSettings();
|
|
2187
2187
|
if (!settings.enabled) return texts.map(() => null);
|
|
2188
2188
|
const results = [];
|
|
2189
|
-
for (const
|
|
2190
|
-
if (!
|
|
2189
|
+
for (const t2 of texts) {
|
|
2190
|
+
if (!t2 || !t2.trim()) {
|
|
2191
2191
|
results.push(null);
|
|
2192
2192
|
continue;
|
|
2193
2193
|
}
|
|
2194
2194
|
try {
|
|
2195
2195
|
let vec = null;
|
|
2196
2196
|
if (settings.endpoint) {
|
|
2197
|
-
vec = await embedTier0(
|
|
2197
|
+
vec = await embedTier0(t2, settings);
|
|
2198
2198
|
}
|
|
2199
|
-
if (!vec) vec = await embedTier1(
|
|
2199
|
+
if (!vec) vec = await embedTier1(t2);
|
|
2200
2200
|
results.push(vec);
|
|
2201
2201
|
} catch {
|
|
2202
2202
|
results.push(null);
|
|
@@ -2411,14 +2411,14 @@ function sanitizeFts5Query(raw) {
|
|
|
2411
2411
|
const trimmed = raw.trim();
|
|
2412
2412
|
if (!trimmed) return '""';
|
|
2413
2413
|
const tokens = trimmed.replace(/"/g, "").split(/\s+/).filter(Boolean);
|
|
2414
|
-
return tokens.map((
|
|
2414
|
+
return tokens.map((t2) => `"${t2}"`).join(" ");
|
|
2415
2415
|
}
|
|
2416
2416
|
function sanitizeFts5QueryOr(raw) {
|
|
2417
2417
|
const trimmed = raw.trim();
|
|
2418
2418
|
if (!trimmed) return '""';
|
|
2419
|
-
const tokens = trimmed.replace(/"/g, "").split(/\s+/).filter((
|
|
2419
|
+
const tokens = trimmed.replace(/"/g, "").split(/\s+/).filter((t2) => t2.replace(/[^a-zA-Z0-9]/g, "").length >= 3);
|
|
2420
2420
|
if (tokens.length === 0) return '""';
|
|
2421
|
-
return tokens.map((
|
|
2421
|
+
return tokens.map((t2) => `"${t2}"`).join(" OR ");
|
|
2422
2422
|
}
|
|
2423
2423
|
function getMemoryDb() {
|
|
2424
2424
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
@@ -3364,9 +3364,9 @@ function upsertObservationEmbedding(db, observationId, vec, modelId, dim) {
|
|
|
3364
3364
|
).run(observationId, modelId, dim, float32ToBlob(vec));
|
|
3365
3365
|
}
|
|
3366
3366
|
function observationEmbedText(title, detail) {
|
|
3367
|
-
const
|
|
3367
|
+
const t2 = (title ?? "").trim();
|
|
3368
3368
|
const d = (detail ?? "").trim();
|
|
3369
|
-
return d ? `${
|
|
3369
|
+
return d ? `${t2}. ${d}` : t2;
|
|
3370
3370
|
}
|
|
3371
3371
|
async function embedMissingObservations(db, opts = {}) {
|
|
3372
3372
|
return runEmbedSweep(
|
|
@@ -4750,7 +4750,7 @@ async function drainSyncQueue(db, deadlineAt) {
|
|
|
4750
4750
|
return out;
|
|
4751
4751
|
}
|
|
4752
4752
|
function sleep(ms) {
|
|
4753
|
-
return new Promise((
|
|
4753
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
4754
4754
|
}
|
|
4755
4755
|
|
|
4756
4756
|
// src/team-rule-sync.ts
|
|
@@ -5953,9 +5953,9 @@ var NOISE_PATTERNS = [
|
|
|
5953
5953
|
/system-reminder/i
|
|
5954
5954
|
];
|
|
5955
5955
|
function isSummarizableSignal(text) {
|
|
5956
|
-
const
|
|
5957
|
-
if (
|
|
5958
|
-
return !NOISE_PATTERNS.some((re) => re.test(
|
|
5956
|
+
const t2 = text.trim();
|
|
5957
|
+
if (t2.length < 25) return false;
|
|
5958
|
+
return !NOISE_PATTERNS.some((re) => re.test(t2));
|
|
5959
5959
|
}
|
|
5960
5960
|
var CREDENTIAL_LABELS = ["api[_-]?key", "secret", "password", "token"];
|
|
5961
5961
|
var LABELLED_CREDENTIAL = new RegExp(
|
|
@@ -6400,9 +6400,403 @@ function backfillQualityScores(db) {
|
|
|
6400
6400
|
|
|
6401
6401
|
// src/cost-reprice.ts
|
|
6402
6402
|
init_config();
|
|
6403
|
-
|
|
6404
|
-
import {
|
|
6403
|
+
import { existsSync as existsSync17 } from "fs";
|
|
6404
|
+
import { dirname as dirname14, join as join12 } from "path";
|
|
6405
|
+
|
|
6406
|
+
// src/db.ts
|
|
6407
|
+
init_db_driver();
|
|
6405
6408
|
import { dirname as dirname13, join as join11 } from "path";
|
|
6409
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync14, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
6410
|
+
|
|
6411
|
+
// src/preflight.ts
|
|
6412
|
+
init_db_driver();
|
|
6413
|
+
init_config();
|
|
6414
|
+
|
|
6415
|
+
// src/db.ts
|
|
6416
|
+
init_config();
|
|
6417
|
+
|
|
6418
|
+
// src/lib/sql-table-names.ts
|
|
6419
|
+
init_config();
|
|
6420
|
+
function t(suffix) {
|
|
6421
|
+
return `${getConfig().toolPrefix}_${suffix}`;
|
|
6422
|
+
}
|
|
6423
|
+
|
|
6424
|
+
// src/db.ts
|
|
6425
|
+
function getDataDb() {
|
|
6426
|
+
const dbPath = getResolvedPaths().dataDbPath;
|
|
6427
|
+
const dir = dirname13(dbPath);
|
|
6428
|
+
if (!existsSync16(dir)) {
|
|
6429
|
+
mkdirSync14(dir, { recursive: true });
|
|
6430
|
+
}
|
|
6431
|
+
const db = openDatabase2(dbPath);
|
|
6432
|
+
db.pragma("journal_mode = WAL");
|
|
6433
|
+
db.pragma("foreign_keys = ON");
|
|
6434
|
+
initDataSchema(db);
|
|
6435
|
+
return db;
|
|
6436
|
+
}
|
|
6437
|
+
function initDataSchema(db) {
|
|
6438
|
+
db.exec(`
|
|
6439
|
+
CREATE TABLE IF NOT EXISTS ${t("imports")} (
|
|
6440
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6441
|
+
source_file TEXT NOT NULL,
|
|
6442
|
+
target_file TEXT NOT NULL,
|
|
6443
|
+
import_type TEXT NOT NULL CHECK(import_type IN ('named', 'default', 'namespace', 'side_effect', 'dynamic')),
|
|
6444
|
+
imported_names TEXT NOT NULL DEFAULT '[]',
|
|
6445
|
+
line INTEGER NOT NULL DEFAULT 0
|
|
6446
|
+
);
|
|
6447
|
+
|
|
6448
|
+
CREATE INDEX IF NOT EXISTS idx_massu_imports_source ON ${t("imports")}(source_file);
|
|
6449
|
+
CREATE INDEX IF NOT EXISTS idx_massu_imports_target ON ${t("imports")}(target_file);
|
|
6450
|
+
|
|
6451
|
+
CREATE TABLE IF NOT EXISTS ${t("trpc_procedures")} (
|
|
6452
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6453
|
+
router_file TEXT NOT NULL,
|
|
6454
|
+
router_name TEXT NOT NULL,
|
|
6455
|
+
procedure_name TEXT NOT NULL,
|
|
6456
|
+
procedure_type TEXT NOT NULL CHECK(procedure_type IN ('query', 'mutation')),
|
|
6457
|
+
has_ui_caller INTEGER NOT NULL DEFAULT 0
|
|
6458
|
+
);
|
|
6459
|
+
|
|
6460
|
+
CREATE INDEX IF NOT EXISTS idx_massu_trpc_router ON ${t("trpc_procedures")}(router_name);
|
|
6461
|
+
|
|
6462
|
+
CREATE TABLE IF NOT EXISTS ${t("trpc_call_sites")} (
|
|
6463
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6464
|
+
procedure_id INTEGER NOT NULL,
|
|
6465
|
+
file TEXT NOT NULL,
|
|
6466
|
+
line INTEGER NOT NULL DEFAULT 0,
|
|
6467
|
+
call_pattern TEXT NOT NULL,
|
|
6468
|
+
FOREIGN KEY (procedure_id) REFERENCES ${t("trpc_procedures")}(id)
|
|
6469
|
+
);
|
|
6470
|
+
|
|
6471
|
+
CREATE INDEX IF NOT EXISTS idx_massu_call_sites_proc ON ${t("trpc_call_sites")}(procedure_id);
|
|
6472
|
+
|
|
6473
|
+
CREATE TABLE IF NOT EXISTS ${t("page_deps")} (
|
|
6474
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6475
|
+
page_file TEXT NOT NULL,
|
|
6476
|
+
route TEXT NOT NULL,
|
|
6477
|
+
portal TEXT NOT NULL DEFAULT 'unknown',
|
|
6478
|
+
components TEXT NOT NULL DEFAULT '[]',
|
|
6479
|
+
hooks TEXT NOT NULL DEFAULT '[]',
|
|
6480
|
+
routers TEXT NOT NULL DEFAULT '[]',
|
|
6481
|
+
tables_touched TEXT NOT NULL DEFAULT '[]'
|
|
6482
|
+
);
|
|
6483
|
+
|
|
6484
|
+
CREATE INDEX IF NOT EXISTS idx_massu_page_deps_page ON ${t("page_deps")}(page_file);
|
|
6485
|
+
|
|
6486
|
+
CREATE TABLE IF NOT EXISTS ${t("middleware_tree")} (
|
|
6487
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6488
|
+
file TEXT NOT NULL UNIQUE
|
|
6489
|
+
);
|
|
6490
|
+
|
|
6491
|
+
CREATE TABLE IF NOT EXISTS ${t("meta")} (
|
|
6492
|
+
key TEXT PRIMARY KEY,
|
|
6493
|
+
value TEXT NOT NULL
|
|
6494
|
+
);
|
|
6495
|
+
|
|
6496
|
+
-- ============================================================
|
|
6497
|
+
-- Python Code Intelligence Tables
|
|
6498
|
+
-- ============================================================
|
|
6499
|
+
|
|
6500
|
+
CREATE TABLE IF NOT EXISTS ${t("py_imports")} (
|
|
6501
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6502
|
+
source_file TEXT NOT NULL,
|
|
6503
|
+
target_file TEXT NOT NULL,
|
|
6504
|
+
import_type TEXT NOT NULL CHECK(import_type IN ('absolute', 'relative', 'from_absolute', 'from_relative')),
|
|
6505
|
+
imported_names TEXT NOT NULL DEFAULT '[]',
|
|
6506
|
+
line INTEGER NOT NULL DEFAULT 0
|
|
6507
|
+
);
|
|
6508
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_imports_source ON ${t("py_imports")}(source_file);
|
|
6509
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_imports_target ON ${t("py_imports")}(target_file);
|
|
6510
|
+
|
|
6511
|
+
CREATE TABLE IF NOT EXISTS ${t("py_meta")} (
|
|
6512
|
+
key TEXT PRIMARY KEY,
|
|
6513
|
+
value TEXT NOT NULL
|
|
6514
|
+
);
|
|
6515
|
+
|
|
6516
|
+
CREATE TABLE IF NOT EXISTS ${t("py_routes")} (
|
|
6517
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6518
|
+
file TEXT NOT NULL,
|
|
6519
|
+
method TEXT NOT NULL,
|
|
6520
|
+
path TEXT NOT NULL,
|
|
6521
|
+
function_name TEXT NOT NULL,
|
|
6522
|
+
dependencies TEXT NOT NULL DEFAULT '[]',
|
|
6523
|
+
request_model TEXT,
|
|
6524
|
+
response_model TEXT,
|
|
6525
|
+
is_authenticated INTEGER NOT NULL DEFAULT 0,
|
|
6526
|
+
line INTEGER NOT NULL DEFAULT 0
|
|
6527
|
+
);
|
|
6528
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_routes_path ON ${t("py_routes")}(path);
|
|
6529
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_routes_file ON ${t("py_routes")}(file);
|
|
6530
|
+
|
|
6531
|
+
CREATE TABLE IF NOT EXISTS ${t("py_route_callers")} (
|
|
6532
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6533
|
+
route_id INTEGER NOT NULL REFERENCES ${t("py_routes")}(id) ON DELETE CASCADE,
|
|
6534
|
+
frontend_file TEXT NOT NULL,
|
|
6535
|
+
line INTEGER NOT NULL DEFAULT 0,
|
|
6536
|
+
call_pattern TEXT NOT NULL
|
|
6537
|
+
);
|
|
6538
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_route_callers_route ON ${t("py_route_callers")}(route_id);
|
|
6539
|
+
|
|
6540
|
+
CREATE TABLE IF NOT EXISTS ${t("py_models")} (
|
|
6541
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6542
|
+
class_name TEXT NOT NULL,
|
|
6543
|
+
table_name TEXT,
|
|
6544
|
+
file TEXT NOT NULL,
|
|
6545
|
+
line INTEGER NOT NULL DEFAULT 0,
|
|
6546
|
+
columns TEXT NOT NULL DEFAULT '[]',
|
|
6547
|
+
relationships TEXT NOT NULL DEFAULT '[]',
|
|
6548
|
+
foreign_keys TEXT NOT NULL DEFAULT '[]'
|
|
6549
|
+
);
|
|
6550
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_models_file ON ${t("py_models")}(file);
|
|
6551
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_models_table ON ${t("py_models")}(table_name);
|
|
6552
|
+
|
|
6553
|
+
CREATE TABLE IF NOT EXISTS ${t("py_fk_edges")} (
|
|
6554
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6555
|
+
source_table TEXT NOT NULL,
|
|
6556
|
+
source_column TEXT NOT NULL,
|
|
6557
|
+
target_table TEXT NOT NULL,
|
|
6558
|
+
target_column TEXT NOT NULL
|
|
6559
|
+
);
|
|
6560
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_fk_source ON ${t("py_fk_edges")}(source_table);
|
|
6561
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_fk_target ON ${t("py_fk_edges")}(target_table);
|
|
6562
|
+
|
|
6563
|
+
CREATE TABLE IF NOT EXISTS ${t("py_migrations")} (
|
|
6564
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6565
|
+
revision TEXT NOT NULL UNIQUE,
|
|
6566
|
+
down_revision TEXT,
|
|
6567
|
+
file TEXT NOT NULL,
|
|
6568
|
+
description TEXT,
|
|
6569
|
+
operations TEXT NOT NULL DEFAULT '[]',
|
|
6570
|
+
is_head INTEGER NOT NULL DEFAULT 0
|
|
6571
|
+
);
|
|
6572
|
+
CREATE INDEX IF NOT EXISTS idx_massu_py_migrations_rev ON ${t("py_migrations")}(revision);
|
|
6573
|
+
|
|
6574
|
+
-- ============================================================
|
|
6575
|
+
-- Sentinel: Feature Registry Tables
|
|
6576
|
+
-- ============================================================
|
|
6577
|
+
|
|
6578
|
+
-- Core feature definition
|
|
6579
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel")} (
|
|
6580
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6581
|
+
feature_key TEXT UNIQUE NOT NULL,
|
|
6582
|
+
domain TEXT NOT NULL,
|
|
6583
|
+
subdomain TEXT,
|
|
6584
|
+
title TEXT NOT NULL,
|
|
6585
|
+
description TEXT,
|
|
6586
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
6587
|
+
CHECK(status IN ('planned', 'active', 'deprecated', 'removed')),
|
|
6588
|
+
priority TEXT DEFAULT 'standard'
|
|
6589
|
+
CHECK(priority IN ('critical', 'standard', 'nice-to-have')),
|
|
6590
|
+
portal_scope TEXT DEFAULT '[]',
|
|
6591
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
6592
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
6593
|
+
removed_at TEXT,
|
|
6594
|
+
removed_reason TEXT
|
|
6595
|
+
);
|
|
6596
|
+
|
|
6597
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_domain ON ${t("sentinel")}(domain);
|
|
6598
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_status ON ${t("sentinel")}(status);
|
|
6599
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_key ON ${t("sentinel")}(feature_key);
|
|
6600
|
+
|
|
6601
|
+
-- Feature-to-component mapping (many-to-many)
|
|
6602
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_components")} (
|
|
6603
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6604
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6605
|
+
component_file TEXT NOT NULL,
|
|
6606
|
+
component_name TEXT,
|
|
6607
|
+
role TEXT DEFAULT 'implementation'
|
|
6608
|
+
CHECK(role IN ('implementation', 'ui', 'data', 'utility')),
|
|
6609
|
+
is_primary BOOLEAN DEFAULT 0,
|
|
6610
|
+
UNIQUE(feature_id, component_file, component_name)
|
|
6611
|
+
);
|
|
6612
|
+
|
|
6613
|
+
-- Feature-to-procedure mapping (many-to-many)
|
|
6614
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_procedures")} (
|
|
6615
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6616
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6617
|
+
router_name TEXT NOT NULL,
|
|
6618
|
+
procedure_name TEXT NOT NULL,
|
|
6619
|
+
procedure_type TEXT,
|
|
6620
|
+
UNIQUE(feature_id, router_name, procedure_name)
|
|
6621
|
+
);
|
|
6622
|
+
|
|
6623
|
+
-- Feature-to-page mapping (where feature is accessible)
|
|
6624
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_pages")} (
|
|
6625
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6626
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6627
|
+
page_route TEXT NOT NULL,
|
|
6628
|
+
portal TEXT,
|
|
6629
|
+
UNIQUE(feature_id, page_route, portal)
|
|
6630
|
+
);
|
|
6631
|
+
|
|
6632
|
+
-- Feature dependency graph
|
|
6633
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_deps")} (
|
|
6634
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6635
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6636
|
+
depends_on_feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6637
|
+
dependency_type TEXT DEFAULT 'requires'
|
|
6638
|
+
CHECK(dependency_type IN ('requires', 'enhances', 'replaces')),
|
|
6639
|
+
UNIQUE(feature_id, depends_on_feature_id)
|
|
6640
|
+
);
|
|
6641
|
+
|
|
6642
|
+
-- Feature change log (audit trail)
|
|
6643
|
+
CREATE TABLE IF NOT EXISTS ${t("sentinel_changelog")} (
|
|
6644
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6645
|
+
feature_id INTEGER NOT NULL REFERENCES ${t("sentinel")}(id) ON DELETE CASCADE,
|
|
6646
|
+
change_type TEXT NOT NULL,
|
|
6647
|
+
changed_by TEXT,
|
|
6648
|
+
change_detail TEXT,
|
|
6649
|
+
commit_hash TEXT,
|
|
6650
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
6651
|
+
);
|
|
6652
|
+
|
|
6653
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_components_file ON ${t("sentinel_components")}(component_file);
|
|
6654
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_procedures_router ON ${t("sentinel_procedures")}(router_name);
|
|
6655
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_pages_route ON ${t("sentinel_pages")}(page_route);
|
|
6656
|
+
CREATE INDEX IF NOT EXISTS idx_sentinel_changelog_feature ON ${t("sentinel_changelog")}(feature_id);
|
|
6657
|
+
`);
|
|
6658
|
+
db.exec(`
|
|
6659
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS ${t("sentinel_fts")} USING fts5(
|
|
6660
|
+
feature_key, title, description, domain, subdomain,
|
|
6661
|
+
content=${t("sentinel")}, content_rowid=id
|
|
6662
|
+
);
|
|
6663
|
+
`);
|
|
6664
|
+
db.exec(`
|
|
6665
|
+
CREATE TRIGGER IF NOT EXISTS massu_sentinel_ai AFTER INSERT ON ${t("sentinel")} BEGIN
|
|
6666
|
+
INSERT INTO ${t("sentinel_fts")}(rowid, feature_key, title, description, domain, subdomain)
|
|
6667
|
+
VALUES (new.id, new.feature_key, new.title, new.description, new.domain, new.subdomain);
|
|
6668
|
+
END;
|
|
6669
|
+
|
|
6670
|
+
CREATE TRIGGER IF NOT EXISTS massu_sentinel_ad AFTER DELETE ON ${t("sentinel")} BEGIN
|
|
6671
|
+
INSERT INTO ${t("sentinel_fts")}(${t("sentinel_fts")}, rowid, feature_key, title, description, domain, subdomain)
|
|
6672
|
+
VALUES ('delete', old.id, old.feature_key, old.title, old.description, old.domain, old.subdomain);
|
|
6673
|
+
END;
|
|
6674
|
+
|
|
6675
|
+
CREATE TRIGGER IF NOT EXISTS massu_sentinel_au AFTER UPDATE ON ${t("sentinel")} BEGIN
|
|
6676
|
+
INSERT INTO ${t("sentinel_fts")}(${t("sentinel_fts")}, rowid, feature_key, title, description, domain, subdomain)
|
|
6677
|
+
VALUES ('delete', old.id, old.feature_key, old.title, old.description, old.domain, old.subdomain);
|
|
6678
|
+
INSERT INTO ${t("sentinel_fts")}(rowid, feature_key, title, description, domain, subdomain)
|
|
6679
|
+
VALUES (new.id, new.feature_key, new.title, new.description, new.domain, new.subdomain);
|
|
6680
|
+
END;
|
|
6681
|
+
`);
|
|
6682
|
+
}
|
|
6683
|
+
|
|
6684
|
+
// src/feature-cost-attribution.ts
|
|
6685
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve8 } from "path";
|
|
6686
|
+
|
|
6687
|
+
// src/sentinel-db.ts
|
|
6688
|
+
init_config();
|
|
6689
|
+
init_memory_db();
|
|
6690
|
+
var PROJECT_ROOT = getProjectRoot();
|
|
6691
|
+
function parsePortalScope(raw) {
|
|
6692
|
+
if (!raw) return [];
|
|
6693
|
+
try {
|
|
6694
|
+
return JSON.parse(raw);
|
|
6695
|
+
} catch {
|
|
6696
|
+
return [];
|
|
6697
|
+
}
|
|
6698
|
+
}
|
|
6699
|
+
function toFeature(row) {
|
|
6700
|
+
return {
|
|
6701
|
+
id: row.id,
|
|
6702
|
+
feature_key: row.feature_key,
|
|
6703
|
+
domain: row.domain,
|
|
6704
|
+
subdomain: row.subdomain || null,
|
|
6705
|
+
title: row.title,
|
|
6706
|
+
description: row.description || null,
|
|
6707
|
+
status: row.status,
|
|
6708
|
+
priority: row.priority,
|
|
6709
|
+
portal_scope: parsePortalScope(row.portal_scope),
|
|
6710
|
+
created_at: row.created_at,
|
|
6711
|
+
updated_at: row.updated_at,
|
|
6712
|
+
removed_at: row.removed_at || null,
|
|
6713
|
+
removed_reason: row.removed_reason || null
|
|
6714
|
+
};
|
|
6715
|
+
}
|
|
6716
|
+
function getFeaturesByFile(db, filePath) {
|
|
6717
|
+
const rows = db.prepare(`
|
|
6718
|
+
SELECT DISTINCT s.* FROM ${t("sentinel")} s
|
|
6719
|
+
JOIN ${t("sentinel_components")} c ON c.feature_id = s.id
|
|
6720
|
+
WHERE c.component_file = ?
|
|
6721
|
+
ORDER BY s.feature_key
|
|
6722
|
+
LIMIT 1000
|
|
6723
|
+
`).all(filePath);
|
|
6724
|
+
return rows.map(toFeature);
|
|
6725
|
+
}
|
|
6726
|
+
|
|
6727
|
+
// src/feature-cost-attribution.ts
|
|
6728
|
+
var UNATTRIBUTED_FEATURE = "(no registered feature)";
|
|
6729
|
+
var MAX_SESSION_FILES = 2e4;
|
|
6730
|
+
function sessionFileTouches(memDb, sessionId, projectRoot) {
|
|
6731
|
+
const rows = memDb.prepare(
|
|
6732
|
+
`SELECT j.value AS file, COUNT(*) AS n
|
|
6733
|
+
FROM tool_call_details t, json_each(t.files_involved) j
|
|
6734
|
+
WHERE t.session_id = ? AND json_valid(t.files_involved)
|
|
6735
|
+
AND json_type(t.files_involved) = 'array' AND j.type = 'text' AND j.value <> ''
|
|
6736
|
+
GROUP BY j.value
|
|
6737
|
+
LIMIT ?`
|
|
6738
|
+
).all(sessionId, MAX_SESSION_FILES + 1);
|
|
6739
|
+
if (rows.length > MAX_SESSION_FILES) {
|
|
6740
|
+
throw new Error(`session ${sessionId} touched more than ${MAX_SESSION_FILES} distinct files; not allocating a truncated list`);
|
|
6741
|
+
}
|
|
6742
|
+
const touches = /* @__PURE__ */ new Map();
|
|
6743
|
+
for (const { file, n } of rows) {
|
|
6744
|
+
const abs = isAbsolute2(file) ? file : resolve8(projectRoot, file);
|
|
6745
|
+
const rel = relative2(projectRoot, abs);
|
|
6746
|
+
const key = rel.startsWith("..") || isAbsolute2(rel) ? abs : rel;
|
|
6747
|
+
touches.set(key, (touches.get(key) ?? 0) + Number(n));
|
|
6748
|
+
}
|
|
6749
|
+
return touches;
|
|
6750
|
+
}
|
|
6751
|
+
function allocateSessionCost(touches, featuresOf, totals) {
|
|
6752
|
+
const units = /* @__PURE__ */ new Map();
|
|
6753
|
+
let all = 0;
|
|
6754
|
+
for (const [file, n] of touches) {
|
|
6755
|
+
all += n;
|
|
6756
|
+
const features = [...new Set(featuresOf(file))];
|
|
6757
|
+
if (features.length === 0) {
|
|
6758
|
+
units.set(UNATTRIBUTED_FEATURE, (units.get(UNATTRIBUTED_FEATURE) ?? 0) + n);
|
|
6759
|
+
} else {
|
|
6760
|
+
for (const k of features) units.set(k, (units.get(k) ?? 0) + n / features.length);
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
if (all === 0) return [{ featureKey: UNATTRIBUTED_FEATURE, share: 1, tokens: totals.tokens, costUsd: totals.costUsd }];
|
|
6764
|
+
return [...units].map(([featureKey, u]) => ({
|
|
6765
|
+
featureKey,
|
|
6766
|
+
share: u / all,
|
|
6767
|
+
tokens: Math.round(totals.tokens * u / all),
|
|
6768
|
+
costUsd: totals.costUsd * u / all
|
|
6769
|
+
})).sort((a, b) => b.costUsd - a.costUsd || a.featureKey.localeCompare(b.featureKey));
|
|
6770
|
+
}
|
|
6771
|
+
function attributeSessionCost(memDb, dataDb, sessionId, projectRoot) {
|
|
6772
|
+
const cost = memDb.prepare(
|
|
6773
|
+
`SELECT total_tokens, estimated_cost_usd, cost_status FROM session_costs WHERE session_id = ?`
|
|
6774
|
+
).get(sessionId);
|
|
6775
|
+
const replace = memDb.transaction((rows2) => {
|
|
6776
|
+
memDb.prepare("DELETE FROM feature_costs WHERE session_id = ?").run(sessionId);
|
|
6777
|
+
const ins = memDb.prepare(
|
|
6778
|
+
"INSERT INTO feature_costs (feature_key, session_id, tokens_used, estimated_cost_usd) VALUES (?, ?, ?, ?)"
|
|
6779
|
+
);
|
|
6780
|
+
for (const r2 of rows2) ins.run(r2.featureKey, sessionId, r2.tokens, r2.costUsd);
|
|
6781
|
+
});
|
|
6782
|
+
if (!cost || cost.cost_status === "unknown") {
|
|
6783
|
+
replace([]);
|
|
6784
|
+
return [];
|
|
6785
|
+
}
|
|
6786
|
+
const touches = sessionFileTouches(memDb, sessionId, projectRoot);
|
|
6787
|
+
const featuresOf = (file) => {
|
|
6788
|
+
const keys = getFeaturesByFile(dataDb, file).map((f) => f.feature_key);
|
|
6789
|
+
if (!isAbsolute2(file)) keys.push(...getFeaturesByFile(dataDb, resolve8(projectRoot, file)).map((f) => f.feature_key));
|
|
6790
|
+
return keys;
|
|
6791
|
+
};
|
|
6792
|
+
const rows = allocateSessionCost(touches, featuresOf, { tokens: cost.total_tokens, costUsd: cost.estimated_cost_usd });
|
|
6793
|
+
replace(rows);
|
|
6794
|
+
return rows;
|
|
6795
|
+
}
|
|
6796
|
+
|
|
6797
|
+
// src/cost-reprice.ts
|
|
6798
|
+
init_hook_failure_signal();
|
|
6799
|
+
init_memory_db();
|
|
6406
6800
|
|
|
6407
6801
|
// src/cost-tracker.ts
|
|
6408
6802
|
init_config();
|
|
@@ -6472,8 +6866,8 @@ function tokenCounts(u) {
|
|
|
6472
6866
|
}
|
|
6473
6867
|
function priceResponse(price, u) {
|
|
6474
6868
|
if (!price) return null;
|
|
6475
|
-
const
|
|
6476
|
-
const prompt =
|
|
6869
|
+
const t2 = tokenCounts(u);
|
|
6870
|
+
const prompt = t2.input + t2.cacheRead + t2.cacheWrite5m + t2.cacheWrite1h;
|
|
6477
6871
|
let rates = price.longPrompt && prompt > price.longPrompt.overTokens ? price.longPrompt.rates : price;
|
|
6478
6872
|
if (u.speed === "fast" && price.fast) {
|
|
6479
6873
|
const k = price.fast.input / rates.input;
|
|
@@ -6486,7 +6880,7 @@ function priceResponse(price, u) {
|
|
|
6486
6880
|
};
|
|
6487
6881
|
}
|
|
6488
6882
|
const geo = u.inference_geo === "us" ? price.usGeoMultiplier ?? 1 : 1;
|
|
6489
|
-
const dollars =
|
|
6883
|
+
const dollars = t2.input * rates.input + t2.output * rates.output + t2.cacheRead * rates.cacheRead + t2.cacheWrite5m * rates.cacheWrite5m + t2.cacheWrite1h * rates.cacheWrite1h;
|
|
6490
6884
|
return dollars / 1e6 * geo;
|
|
6491
6885
|
}
|
|
6492
6886
|
|
|
@@ -6520,12 +6914,12 @@ function costTranscriptEntries(entries, opts = {}) {
|
|
|
6520
6914
|
out.lastMessageId = msg.id;
|
|
6521
6915
|
}
|
|
6522
6916
|
const usage = msg.usage;
|
|
6523
|
-
const
|
|
6524
|
-
if (
|
|
6525
|
-
out.inputTokens +=
|
|
6526
|
-
out.outputTokens +=
|
|
6527
|
-
out.cacheReadTokens +=
|
|
6528
|
-
out.cacheWriteTokens +=
|
|
6917
|
+
const t2 = tokenCounts(usage);
|
|
6918
|
+
if (t2.input + t2.output + t2.cacheRead + t2.cacheWrite5m + t2.cacheWrite1h === 0) continue;
|
|
6919
|
+
out.inputTokens += t2.input;
|
|
6920
|
+
out.outputTokens += t2.output;
|
|
6921
|
+
out.cacheReadTokens += t2.cacheRead;
|
|
6922
|
+
out.cacheWriteTokens += t2.cacheWrite5m + t2.cacheWrite1h;
|
|
6529
6923
|
const dollars = priceResponse(resolveModelPrice(msg.model, overrides), usage);
|
|
6530
6924
|
if (dollars === null) {
|
|
6531
6925
|
out.unpricedResponses++;
|
|
@@ -6605,7 +6999,7 @@ function markSessionCostUnknown(db, sessionId) {
|
|
|
6605
6999
|
var cursorKey = (sid) => `cost_processed_line:${sid}`;
|
|
6606
7000
|
var lastIdKey = (sid) => `cost_last_message:${sid}`;
|
|
6607
7001
|
var sessionLeaseKey = (sid) => `session_end_lease:${sid}`;
|
|
6608
|
-
async function updateSessionCost(db, sessionId, transcriptPath) {
|
|
7002
|
+
async function updateSessionCost(db, sessionId, transcriptPath, attribute = attributeFeatureCosts) {
|
|
6609
7003
|
const row = db.prepare("SELECT pricing_version FROM session_costs WHERE session_id = ?").get(sessionId);
|
|
6610
7004
|
const cursor = Number(getMemoryMeta(db, cursorKey(sessionId)) ?? "0") || 0;
|
|
6611
7005
|
const incremental = row?.pricing_version === PRICING_VERSION && cursor > 0;
|
|
@@ -6617,11 +7011,23 @@ async function updateSessionCost(db, sessionId, transcriptPath) {
|
|
|
6617
7011
|
storeSessionCost(db, sessionId, slice, incremental ? "add" : "replace");
|
|
6618
7012
|
setMemoryMeta(db, cursorKey(sessionId), String(totalLines));
|
|
6619
7013
|
if (slice.lastMessageId) setMemoryMeta(db, lastIdKey(sessionId), slice.lastMessageId);
|
|
7014
|
+
attribute(db, sessionId);
|
|
6620
7015
|
}
|
|
7016
|
+
var attributeFeatureCosts = (db, sessionId) => {
|
|
7017
|
+
let dataDb = null;
|
|
7018
|
+
try {
|
|
7019
|
+
dataDb = getDataDb();
|
|
7020
|
+
attributeSessionCost(db, dataDb, sessionId, getProjectRoot());
|
|
7021
|
+
} catch (err) {
|
|
7022
|
+
recordHookFailure("cost:feature-attribution", err, { sessionId });
|
|
7023
|
+
} finally {
|
|
7024
|
+
dataDb?.close();
|
|
7025
|
+
}
|
|
7026
|
+
};
|
|
6621
7027
|
function defaultTranscriptFor(sessionId) {
|
|
6622
7028
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) return null;
|
|
6623
|
-
const path =
|
|
6624
|
-
return
|
|
7029
|
+
const path = join12(dirname14(getResolvedPaths().memoryDir), `${sessionId}.jsonl`);
|
|
7030
|
+
return existsSync17(path) ? path : null;
|
|
6625
7031
|
}
|
|
6626
7032
|
async function repriceStoredSessions(db, opts = {}) {
|
|
6627
7033
|
const deadlineAt = opts.deadlineAt ?? Infinity;
|
|
@@ -6645,10 +7051,11 @@ async function repriceStoredSessions(db, opts = {}) {
|
|
|
6645
7051
|
try {
|
|
6646
7052
|
const path = find(sid);
|
|
6647
7053
|
if (path) {
|
|
6648
|
-
await updateSessionCost(db, sid, path);
|
|
7054
|
+
await updateSessionCost(db, sid, path, opts.attribute);
|
|
6649
7055
|
out.repriced++;
|
|
6650
7056
|
} else {
|
|
6651
7057
|
markSessionCostUnknown(db, sid);
|
|
7058
|
+
(opts.attribute ?? attributeFeatureCosts)(db, sid);
|
|
6652
7059
|
out.markedUnknown++;
|
|
6653
7060
|
}
|
|
6654
7061
|
} catch (err) {
|
|
@@ -6781,8 +7188,8 @@ init_memory_vector();
|
|
|
6781
7188
|
function temporalPredicate(asOf, includeSuperseded, prefix) {
|
|
6782
7189
|
const c = (name) => `${prefix}${name}`;
|
|
6783
7190
|
if (asOf != null && Number.isFinite(asOf)) {
|
|
6784
|
-
const
|
|
6785
|
-
return `${c("ingested_at_epoch")} <= ${
|
|
7191
|
+
const t2 = Math.floor(Number(asOf) / 1e3);
|
|
7192
|
+
return `${c("ingested_at_epoch")} <= ${t2} AND (${c("expired_at_epoch")} IS NULL OR ${c("expired_at_epoch")} > ${t2}) AND ${c("valid_from_epoch")} <= ${t2} AND (${c("valid_to_epoch")} IS NULL OR ${c("valid_to_epoch")} > ${t2})`;
|
|
6786
7193
|
}
|
|
6787
7194
|
if (!includeSuperseded) {
|
|
6788
7195
|
return `${c("expired_at_epoch")} IS NULL`;
|
|
@@ -6878,7 +7285,7 @@ function collectObservations(memDb, queryText, pool, nowMs, loadVec, modelId, di
|
|
|
6878
7285
|
}
|
|
6879
7286
|
function collectLikeSource(db, source, cfg, queryText, pool, nowMs) {
|
|
6880
7287
|
const map = /* @__PURE__ */ new Map();
|
|
6881
|
-
const tokens = queryText.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((
|
|
7288
|
+
const tokens = queryText.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t2) => t2.length >= 3).slice(0, 8);
|
|
6882
7289
|
const ageExpr = (created) => {
|
|
6883
7290
|
const ms = cfg.createdIsEpoch ? Number(created) * 1e3 : Date.parse(String(created));
|
|
6884
7291
|
return Number.isFinite(ms) ? ageDaysFrom(ms, nowMs) : 0;
|
|
@@ -6886,7 +7293,7 @@ function collectLikeSource(db, source, cfg, queryText, pool, nowMs) {
|
|
|
6886
7293
|
if (tokens.length > 0) {
|
|
6887
7294
|
const likeClause = cfg.searchCols.map((col) => tokens.map(() => `${col} LIKE ?`).join(" OR ")).join(" OR ");
|
|
6888
7295
|
const params = [];
|
|
6889
|
-
for (const _col of cfg.searchCols) for (const
|
|
7296
|
+
for (const _col of cfg.searchCols) for (const t2 of tokens) params.push(`%${t2}%`);
|
|
6890
7297
|
try {
|
|
6891
7298
|
const rows = db.prepare(
|
|
6892
7299
|
`SELECT ${cfg.idCol} AS id, ${cfg.titleExpr} AS title,
|
|
@@ -7304,8 +7711,8 @@ ${d.decision}`.trim(),
|
|
|
7304
7711
|
// src/memory-consolidate.ts
|
|
7305
7712
|
init_memory_db();
|
|
7306
7713
|
import { createHash as createHash6 } from "crypto";
|
|
7307
|
-
import { existsSync as
|
|
7308
|
-
import { join as
|
|
7714
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
7715
|
+
import { join as join13 } from "path";
|
|
7309
7716
|
init_memory_embedder();
|
|
7310
7717
|
init_memory_vector();
|
|
7311
7718
|
var LEASE_KEY = "consolidate_lease";
|
|
@@ -7466,7 +7873,7 @@ ${r2.detail ?? ""}`.trim());
|
|
|
7466
7873
|
}
|
|
7467
7874
|
clusters.push(cluster);
|
|
7468
7875
|
}
|
|
7469
|
-
const candidateDir =
|
|
7876
|
+
const candidateDir = join13(projectRoot, ".massu", "rule-candidates");
|
|
7470
7877
|
let promoted = 0;
|
|
7471
7878
|
for (const cluster of clusters) {
|
|
7472
7879
|
const occurrences = cluster.reduce((n, c) => n + (c.recurrence_count || 1), 0);
|
|
@@ -7479,13 +7886,13 @@ ${r2.detail ?? ""}`.trim());
|
|
|
7479
7886
|
${representative.detail}` : ""}`;
|
|
7480
7887
|
const clusterKey = cluster.map((c) => c.title.toLowerCase().replace(/\s+/g, " ").trim()).sort().join("|");
|
|
7481
7888
|
const promptHash = createHash6("sha256").update(clusterKey).digest("hex").slice(0, 16);
|
|
7482
|
-
const candidatePath =
|
|
7483
|
-
if (
|
|
7889
|
+
const candidatePath = join13(candidateDir, `${promptHash}.json`);
|
|
7890
|
+
if (existsSync18(candidatePath)) continue;
|
|
7484
7891
|
if (dryRun) {
|
|
7485
7892
|
promoted++;
|
|
7486
7893
|
continue;
|
|
7487
7894
|
}
|
|
7488
|
-
|
|
7895
|
+
mkdirSync15(candidateDir, { recursive: true });
|
|
7489
7896
|
writeFileSync10(
|
|
7490
7897
|
candidatePath,
|
|
7491
7898
|
JSON.stringify(
|
|
@@ -8084,14 +8491,14 @@ function extractFilesFromToolCall(toolName, input) {
|
|
|
8084
8491
|
return [];
|
|
8085
8492
|
}
|
|
8086
8493
|
function readStdin() {
|
|
8087
|
-
return new Promise((
|
|
8494
|
+
return new Promise((resolve9) => {
|
|
8088
8495
|
let data = "";
|
|
8089
8496
|
process.stdin.setEncoding("utf-8");
|
|
8090
8497
|
process.stdin.on("data", (chunk) => {
|
|
8091
8498
|
data += chunk;
|
|
8092
8499
|
});
|
|
8093
|
-
process.stdin.on("end", () =>
|
|
8094
|
-
setTimeout(() =>
|
|
8500
|
+
process.stdin.on("end", () => resolve9(data));
|
|
8501
|
+
setTimeout(() => resolve9(data), 5e3);
|
|
8095
8502
|
});
|
|
8096
8503
|
}
|
|
8097
8504
|
if (isDirectInvocation(import.meta.url)) {
|