@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.
@@ -1648,9 +1648,9 @@ function nackLearning(db, lease, error) {
1648
1648
  return snap;
1649
1649
  }
1650
1650
  function undeliveredSnapshot(db) {
1651
- const count = (t) => {
1652
- if (!tableExists(db, t)) return 0;
1653
- const r2 = db.prepare(`SELECT COUNT(*) AS n FROM ${t}`).get();
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 t of RULE_OUTBOUND_TABLES) {
1662
- if (!tableExists(db, t)) continue;
1663
- if (!columns(db, t).has("attempts")) continue;
1664
- const r2 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${t}`).get();
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 t of basic) {
1962
- for (const sub of wordpieceTokenize(t, vocab)) wpTokens.push(sub);
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((t) => vocab.get(t) ?? unkId);
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 t = 0; t < seqLen; t++) {
2070
- const m = mask[t];
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 = t * hiddenSize;
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 t = text.trim();
2082
- if (!t) return [];
2083
- if (t.length <= CHUNK_TARGET_CHARS) return [t];
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 < t.length && chunks.length < MAX_CHUNKS_PER_RECORD) {
2087
- let end = Math.min(start + CHUNK_TARGET_CHARS, t.length);
2088
- if (end < t.length) {
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 = t.slice(start, end);
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 = t.slice(start, end).trim();
2102
+ const chunk = t2.slice(start, end).trim();
2103
2103
  if (chunk) chunks.push(chunk);
2104
- if (end >= t.length) break;
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 t of texts) {
2190
- if (!t || !t.trim()) {
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(t, settings);
2197
+ vec = await embedTier0(t2, settings);
2198
2198
  }
2199
- if (!vec) vec = await embedTier1(t);
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((t) => `"${t}"`).join(" ");
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((t) => t.replace(/[^a-zA-Z0-9]/g, "").length >= 3);
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((t) => `"${t}"`).join(" OR ");
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 t = (title ?? "").trim();
3367
+ const t2 = (title ?? "").trim();
3368
3368
  const d = (detail ?? "").trim();
3369
- return d ? `${t}. ${d}` : t;
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((resolve8) => setTimeout(resolve8, ms));
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 t = text.trim();
5957
- if (t.length < 25) return false;
5958
- return !NOISE_PATTERNS.some((re) => re.test(t));
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
- init_memory_db();
6404
- import { existsSync as existsSync16 } from "fs";
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 t = tokenCounts(u);
6476
- const prompt = t.input + t.cacheRead + t.cacheWrite5m + t.cacheWrite1h;
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 = t.input * rates.input + t.output * rates.output + t.cacheRead * rates.cacheRead + t.cacheWrite5m * rates.cacheWrite5m + t.cacheWrite1h * rates.cacheWrite1h;
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 t = tokenCounts(usage);
6524
- if (t.input + t.output + t.cacheRead + t.cacheWrite5m + t.cacheWrite1h === 0) continue;
6525
- out.inputTokens += t.input;
6526
- out.outputTokens += t.output;
6527
- out.cacheReadTokens += t.cacheRead;
6528
- out.cacheWriteTokens += t.cacheWrite5m + t.cacheWrite1h;
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 = join11(dirname13(getResolvedPaths().memoryDir), `${sessionId}.jsonl`);
6624
- return existsSync16(path) ? path : null;
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 t = Math.floor(Number(asOf) / 1e3);
6785
- return `${c("ingested_at_epoch")} <= ${t} AND (${c("expired_at_epoch")} IS NULL OR ${c("expired_at_epoch")} > ${t}) AND ${c("valid_from_epoch")} <= ${t} AND (${c("valid_to_epoch")} IS NULL OR ${c("valid_to_epoch")} > ${t})`;
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((t) => t.length >= 3).slice(0, 8);
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 t of tokens) params.push(`%${t}%`);
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 existsSync17, mkdirSync as mkdirSync14, writeFileSync as writeFileSync10 } from "fs";
7308
- import { join as join12 } from "path";
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 = join12(projectRoot, ".massu", "rule-candidates");
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 = join12(candidateDir, `${promptHash}.json`);
7483
- if (existsSync17(candidatePath)) continue;
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
- mkdirSync14(candidateDir, { recursive: true });
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((resolve8) => {
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", () => resolve8(data));
8094
- setTimeout(() => resolve8(data), 5e3);
8500
+ process.stdin.on("end", () => resolve9(data));
8501
+ setTimeout(() => resolve9(data), 5e3);
8095
8502
  });
8096
8503
  }
8097
8504
  if (isDirectInvocation(import.meta.url)) {