@massu/core 2.5.4 → 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/dist/cli.js CHANGED
@@ -421,12 +421,12 @@ function runRebuild(pkgDir, abiFrom, abiTo, start) {
421
421
  }
422
422
  const prebuildBin = resolveLocalBin(pkgDir, "prebuild-install");
423
423
  if (prebuildBin) {
424
- const r2 = spawnSync2(process.execPath, [prebuildBin], {
424
+ const r3 = spawnSync2(process.execPath, [prebuildBin], {
425
425
  cwd: pkgDir,
426
426
  encoding: "utf-8",
427
427
  timeout: 12e4
428
428
  });
429
- if (r2.status === 0) {
429
+ if (r3.status === 0) {
430
430
  const res2 = { healed: true, method: "prebuild-install", abiFrom, abiTo, durationMs: Date.now() - start };
431
431
  recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
432
432
  return res2;
@@ -434,12 +434,12 @@ function runRebuild(pkgDir, abiFrom, abiTo, start) {
434
434
  }
435
435
  const gypBin = resolveNodeGypBin();
436
436
  if (gypBin && hasCompiler()) {
437
- const r2 = spawnSync2(process.execPath, [gypBin, "rebuild", "--release"], {
437
+ const r3 = spawnSync2(process.execPath, [gypBin, "rebuild", "--release"], {
438
438
  cwd: pkgDir,
439
439
  encoding: "utf-8",
440
440
  timeout: 3e5
441
441
  });
442
- if (r2.status === 0) {
442
+ if (r3.status === 0) {
443
443
  const res2 = { healed: true, method: "node-gyp", abiFrom, abiTo, durationMs: Date.now() - start };
444
444
  recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
445
445
  return res2;
@@ -518,8 +518,8 @@ function resolveNodeGypBin() {
518
518
  function hasCompiler() {
519
519
  for (const cc of ["cc", "clang", "gcc"]) {
520
520
  try {
521
- const r2 = spawnSync2(cc, ["--version"], { encoding: "utf-8", timeout: 5e3 });
522
- if (r2.status === 0) return true;
521
+ const r3 = spawnSync2(cc, ["--version"], { encoding: "utf-8", timeout: 5e3 });
522
+ if (r3.status === 0) return true;
523
523
  } catch {
524
524
  }
525
525
  }
@@ -593,10 +593,10 @@ function openNodeSqlite(dbPath, opts) {
593
593
  const st = raw.prepare(sql);
594
594
  return {
595
595
  run: (...p19) => {
596
- const r2 = st.run(...p19);
596
+ const r3 = st.run(...p19);
597
597
  return {
598
- changes: typeof r2.changes === "bigint" ? Number(r2.changes) : r2.changes,
599
- lastInsertRowid: r2.lastInsertRowid
598
+ changes: typeof r3.changes === "bigint" ? Number(r3.changes) : r3.changes,
599
+ lastInsertRowid: r3.lastInsertRowid
600
600
  };
601
601
  },
602
602
  get: (...p19) => plainRow(st.get(...p19)),
@@ -1170,7 +1170,8 @@ var init_config = __esm({
1170
1170
  input_per_million: z2.number(),
1171
1171
  output_per_million: z2.number(),
1172
1172
  cache_read_per_million: z2.number().optional(),
1173
- cache_write_per_million: z2.number().optional()
1173
+ cache_write_per_million: z2.number().optional(),
1174
+ cache_write_1h_per_million: z2.number().optional()
1174
1175
  });
1175
1176
  AnalyticsConfigSchema = z2.object({
1176
1177
  quality: z2.object({
@@ -1793,7 +1794,7 @@ function tableExists(db, table) {
1793
1794
  }
1794
1795
  function columns(db, table) {
1795
1796
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
1796
- return new Set(rows.map((r2) => r2.name));
1797
+ return new Set(rows.map((r3) => r3.name));
1797
1798
  }
1798
1799
  function migrateRuleDelivery(db) {
1799
1800
  const ADDITIONS = [
@@ -1814,8 +1815,8 @@ function migrateRuleDelivery(db) {
1814
1815
  function undeliveredSnapshot(db) {
1815
1816
  const count = (t2) => {
1816
1817
  if (!tableExists(db, t2)) return 0;
1817
- const r2 = db.prepare(`SELECT COUNT(*) AS n FROM ${t2}`).get();
1818
- return r2.n;
1818
+ const r3 = db.prepare(`SELECT COUNT(*) AS n FROM ${t2}`).get();
1819
+ return r3.n;
1819
1820
  };
1820
1821
  const promotions = count("team_promotion_outbound");
1821
1822
  const revocations = count("team_revocation_outbound");
@@ -1825,9 +1826,9 @@ function undeliveredSnapshot(db) {
1825
1826
  for (const t2 of RULE_OUTBOUND_TABLES) {
1826
1827
  if (!tableExists(db, t2)) continue;
1827
1828
  if (!columns(db, t2).has("attempts")) continue;
1828
- const r2 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${t2}`).get();
1829
- if (r2.a !== null && r2.a > maxAttempts) maxAttempts = r2.a;
1830
- if (r2.c !== null && (oldest === null || r2.c < oldest)) oldest = r2.c;
1829
+ const r3 = db.prepare(`SELECT MAX(attempts) AS a, MIN(created_at) AS c FROM ${t2}`).get();
1830
+ if (r3.a !== null && r3.a > maxAttempts) maxAttempts = r3.a;
1831
+ if (r3.c !== null && (oldest === null || r3.c < oldest)) oldest = r3.c;
1831
1832
  }
1832
1833
  return {
1833
1834
  promotions,
@@ -1918,9 +1919,9 @@ function setCandidateStatus(db, promptHash, status, destination) {
1918
1919
  }
1919
1920
  function getCandidate(db, promptHash) {
1920
1921
  ensureRuleCandidatesTable(db);
1921
- const r2 = db.prepare("SELECT * FROM rule_candidates WHERE prompt_hash = ?").get(promptHash);
1922
- if (!r2) return null;
1923
- return { ...r2, payload: safeParse(r2.payload_json) };
1922
+ const r3 = db.prepare("SELECT * FROM rule_candidates WHERE prompt_hash = ?").get(promptHash);
1923
+ if (!r3) return null;
1924
+ return { ...r3, payload: safeParse(r3.payload_json) };
1924
1925
  }
1925
1926
  function safeParse(raw) {
1926
1927
  try {
@@ -2138,11 +2139,11 @@ function getActiveEmbedModel() {
2138
2139
  }
2139
2140
  function loadEmbedSettings() {
2140
2141
  try {
2141
- const r2 = getConfig().memory?.recall;
2142
+ const r3 = getConfig().memory?.recall;
2142
2143
  return {
2143
- enabled: r2?.embedEnabled ?? true,
2144
- endpoint: r2?.embedEndpoint,
2145
- model: r2?.embedModel
2144
+ enabled: r3?.embedEnabled ?? true,
2145
+ endpoint: r3?.embedEndpoint,
2146
+ model: r3?.embedModel
2146
2147
  };
2147
2148
  } catch {
2148
2149
  return { enabled: true };
@@ -2416,13 +2417,13 @@ async function runEmbedSweep(db, cfg, opts = {}) {
2416
2417
  break;
2417
2418
  }
2418
2419
  const units = [];
2419
- for (const r2 of rows) {
2420
+ for (const r3 of rows) {
2420
2421
  if (cfg.chunked) {
2421
- const parts = chunkForEmbedding(r2.text);
2422
+ const parts = chunkForEmbedding(r3.text);
2422
2423
  if (parts.length === 0) continue;
2423
- parts.forEach((text18, chunkIx) => units.push({ id: r2.id, chunkIx, text: text18 }));
2424
+ parts.forEach((text18, chunkIx) => units.push({ id: r3.id, chunkIx, text: text18 }));
2424
2425
  } else {
2425
- units.push({ id: r2.id, chunkIx: 0, text: r2.text });
2426
+ units.push({ id: r3.id, chunkIx: 0, text: r3.text });
2426
2427
  }
2427
2428
  }
2428
2429
  const vecs = await embedBatch(units.map((u2) => u2.text));
@@ -2541,6 +2542,7 @@ __export(memory_db_exports, {
2541
2542
  pruneToolCostEvents: () => pruneToolCostEvents,
2542
2543
  recordRecallHits: () => recordRecallHits,
2543
2544
  recordTelemetry: () => recordTelemetry,
2545
+ releaseMetaLease: () => releaseMetaLease,
2544
2546
  removePendingSync: () => removePendingSync,
2545
2547
  sanitizeFts5Query: () => sanitizeFts5Query,
2546
2548
  sanitizeFts5QueryOr: () => sanitizeFts5QueryOr,
@@ -2549,6 +2551,7 @@ __export(memory_db_exports, {
2549
2551
  searchObservations: () => searchObservations,
2550
2552
  setLastProcessedLine: () => setLastProcessedLine,
2551
2553
  setMemoryMeta: () => setMemoryMeta,
2554
+ tryAcquireMetaLease: () => tryAcquireMetaLease,
2552
2555
  upsertObservationEmbedding: () => upsertObservationEmbedding,
2553
2556
  usageWarmupElapsed: () => usageWarmupElapsed
2554
2557
  });
@@ -3054,6 +3057,15 @@ function initMemorySchema(db) {
3054
3057
  );
3055
3058
  CREATE INDEX IF NOT EXISTS idx_sc_session ON session_costs(session_id);
3056
3059
  `);
3060
+ const costCols = db.prepare(`PRAGMA table_info(session_costs)`).all();
3061
+ const addCostCol = (name, ddl) => {
3062
+ if (!costCols.some((c2) => c2.name === name)) db.exec(`ALTER TABLE session_costs ADD COLUMN ${ddl}`);
3063
+ };
3064
+ addCostCol("cost_status", `cost_status TEXT NOT NULL DEFAULT 'unknown'`);
3065
+ addCostCol("unpriced_models", `unpriced_models TEXT NOT NULL DEFAULT ''`);
3066
+ addCostCol("priced_responses", `priced_responses INTEGER NOT NULL DEFAULT 0`);
3067
+ addCostCol("unpriced_responses", `unpriced_responses INTEGER NOT NULL DEFAULT 0`);
3068
+ addCostCol("pricing_version", `pricing_version INTEGER NOT NULL DEFAULT 0`);
3057
3069
  db.exec(`
3058
3070
  CREATE TABLE IF NOT EXISTS feature_costs (
3059
3071
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -3483,6 +3495,18 @@ function getMemoryMeta(db, key) {
3483
3495
  function setMemoryMeta(db, key, value) {
3484
3496
  db.prepare("INSERT OR REPLACE INTO memory_meta (key, value) VALUES (?, ?)").run(key, value);
3485
3497
  }
3498
+ function tryAcquireMetaLease(db, key, ttlSec, nowSec = Math.floor(Date.now() / 1e3)) {
3499
+ const r3 = db.prepare(
3500
+ `INSERT INTO memory_meta (key, value) VALUES (?, ?)
3501
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
3502
+ WHERE memory_meta.value = ''
3503
+ OR CAST(substr(memory_meta.value, instr(memory_meta.value, ':') + 1) AS INTEGER) <= ?`
3504
+ ).run(key, `${process.pid}:${nowSec + ttlSec}`, nowSec);
3505
+ return Number(r3.changes) === 1;
3506
+ }
3507
+ function releaseMetaLease(db, key) {
3508
+ db.prepare(`UPDATE memory_meta SET value = '' WHERE key = ? AND value LIKE ?`).run(key, `${process.pid}:%`);
3509
+ }
3486
3510
  function upsertObservationEmbedding(db, observationId, vec, modelId, dim) {
3487
3511
  db.prepare(
3488
3512
  `INSERT OR REPLACE INTO observation_embeddings (observation_id, model_id, dim, vec, created_at)
@@ -3524,7 +3548,7 @@ async function embedMissingObservations(db, opts = {}) {
3524
3548
  )
3525
3549
  ORDER BY o.id LIMIT ?`
3526
3550
  ).all(cursor, batchSize);
3527
- return rows.map((r2) => ({ id: r2.id, text: observationEmbedText(r2.title, r2.detail) }));
3551
+ return rows.map((r3) => ({ id: r3.id, text: observationEmbedText(r3.title, r3.detail) }));
3528
3552
  }
3529
3553
  },
3530
3554
  opts
@@ -3612,9 +3636,9 @@ function rescueLearningFromStalePayloads(db, stale) {
3612
3636
  rescued++;
3613
3637
  }
3614
3638
  const revocations = Array.isArray(payload.rule_revocations) ? payload.rule_revocations : [];
3615
- for (const r2 of revocations) {
3616
- if (typeof r2?.prompt_hash === "string") {
3617
- enqueueTeamRevocation(db, r2.prompt_hash);
3639
+ for (const r3 of revocations) {
3640
+ if (typeof r3?.prompt_hash === "string") {
3641
+ enqueueTeamRevocation(db, r3.prompt_hash);
3618
3642
  rescued++;
3619
3643
  }
3620
3644
  }
@@ -4351,7 +4375,7 @@ var init_memory_db = __esm({
4351
4375
  init_rule_candidate_store();
4352
4376
  init_hook_failure_signal();
4353
4377
  init_memory_embed_sweep();
4354
- MEMORY_SCHEMA_VERSION = 1;
4378
+ MEMORY_SCHEMA_VERSION = 2;
4355
4379
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
4356
4380
  MAX_DRAFT_TEXT_LEN = 16384;
4357
4381
  FUNNEL_EVENT_OUTBOX_CAP = 2e4;
@@ -5831,7 +5855,7 @@ var init_install_commands = __esm({
5831
5855
  });
5832
5856
 
5833
5857
  // src/lib/hook-timeouts.ts
5834
- var HOOK_TIMEOUTS;
5858
+ var HOOK_TIMEOUTS, BACKGROUND_HOOKS;
5835
5859
  var init_hook_timeouts = __esm({
5836
5860
  "src/lib/hook-timeouts.ts"() {
5837
5861
  "use strict";
@@ -5865,6 +5889,43 @@ var init_hook_timeouts = __esm({
5865
5889
  // always fails open on its own terms before the platform kills it.
5866
5890
  "memory-recall": 10
5867
5891
  };
5892
+ BACKGROUND_HOOKS = /* @__PURE__ */ new Set(["session-end"]);
5893
+ }
5894
+ });
5895
+
5896
+ // src/lib/hook-registry.ts
5897
+ function getExpectedHookFiles() {
5898
+ return REGISTERED_HOOKS.map((name) => `${name}.js`);
5899
+ }
5900
+ var REGISTERED_HOOKS, DECISION_HOOKS;
5901
+ var init_hook_registry = __esm({
5902
+ "src/lib/hook-registry.ts"() {
5903
+ "use strict";
5904
+ REGISTERED_HOOKS = [
5905
+ "auto-learning-pipeline",
5906
+ "classify-failure",
5907
+ "cost-tracker",
5908
+ "fix-detector",
5909
+ "incident-pipeline",
5910
+ "intent-suggester",
5911
+ "memory-recall",
5912
+ "post-edit-context",
5913
+ "post-tool-use",
5914
+ "pre-compact",
5915
+ // P-E-019 (1.12.0): security-gate + pre-delete-check are kept registered
5916
+ // for back-compat (operator settings.json may still reference them
5917
+ // individually), but new installs emit only `pre-tool-use-gate` —
5918
+ // the consolidated single-spawn PreToolUse hook.
5919
+ "pre-delete-check",
5920
+ "pre-tool-use-gate",
5921
+ "quality-event",
5922
+ "rule-enforcement-pipeline",
5923
+ "security-gate",
5924
+ "session-end",
5925
+ "session-start",
5926
+ "user-prompt"
5927
+ ];
5928
+ DECISION_HOOKS = ["pre-delete-check", "pre-tool-use-gate", "security-gate"];
5868
5929
  }
5869
5930
  });
5870
5931
 
@@ -6583,13 +6644,13 @@ var init_package_detector = __esm({
6583
6644
  // src/detect/framework-detector.ts
6584
6645
  function matchRule(rules, language, kind, deps) {
6585
6646
  let best = null;
6586
- for (const r2 of rules) {
6587
- if (r2.language !== language) continue;
6588
- if (r2.kind !== kind) continue;
6589
- if (!deps.has(r2.keyword.toLowerCase())) continue;
6590
- const pr = r2.priority ?? 0;
6647
+ for (const r3 of rules) {
6648
+ if (r3.language !== language) continue;
6649
+ if (r3.kind !== kind) continue;
6650
+ if (!deps.has(r3.keyword.toLowerCase())) continue;
6651
+ const pr = r3.priority ?? 0;
6591
6652
  if (!best || pr > best.priority) {
6592
- best = { value: r2.value, priority: pr };
6653
+ best = { value: r3.value, priority: pr };
6593
6654
  }
6594
6655
  }
6595
6656
  return best;
@@ -15121,12 +15182,12 @@ async function introspectAsync(detection, projectRoot) {
15121
15182
  }
15122
15183
  return out;
15123
15184
  }
15124
- function serializeAdapterBlock(r2) {
15125
- const block = { ...r2.conventions };
15126
- if (Object.keys(r2._provenance).length > 0) {
15127
- block._provenance = r2._provenance;
15185
+ function serializeAdapterBlock(r3) {
15186
+ const block = { ...r3.conventions };
15187
+ if (Object.keys(r3._provenance).length > 0) {
15188
+ block._provenance = r3._provenance;
15128
15189
  }
15129
- block._confidence = r2.confidence;
15190
+ block._confidence = r3.confidence;
15130
15191
  return block;
15131
15192
  }
15132
15193
  var FIRST_PARTY_ADAPTERS;
@@ -16058,7 +16119,7 @@ var init_dist2 = __esm({
16058
16119
  }
16059
16120
  };
16060
16121
  k2 = (s) => {
16061
- const { cursor: n, options: t2, style: i } = s, r2 = s.maxItems ?? Number.POSITIVE_INFINITY, c2 = Math.max(process.stdout.rows - 4, 0), o = Math.min(c2, Math.max(r2, 5));
16122
+ const { cursor: n, options: t2, style: i } = s, r3 = s.maxItems ?? Number.POSITIVE_INFINITY, c2 = Math.max(process.stdout.rows - 4, 0), o = Math.min(c2, Math.max(r3, 5));
16062
16123
  let l2 = 0;
16063
16124
  n >= l2 + o - 3 ? l2 = Math.max(Math.min(n - o + 3, t2.length - o), 0) : n < l2 + 2 && (l2 = Math.max(n - 2, 0));
16064
16125
  const $2 = o < t2.length && l2 > 0, d2 = o < t2.length && l2 + o < t2.length;
@@ -16114,12 +16175,12 @@ ${import_picocolors2.default.cyan(m2)}
16114
16175
  return new fD({ active: n, inactive: t2, initialValue: s.initialValue ?? true, render() {
16115
16176
  const i = `${import_picocolors2.default.gray(a)}
16116
16177
  ${y2(this.state)} ${s.message}
16117
- `, r2 = this.value ? n : t2;
16178
+ `, r3 = this.value ? n : t2;
16118
16179
  switch (this.state) {
16119
16180
  case "submit":
16120
- return `${i}${import_picocolors2.default.gray(a)} ${import_picocolors2.default.dim(r2)}`;
16181
+ return `${i}${import_picocolors2.default.gray(a)} ${import_picocolors2.default.dim(r3)}`;
16121
16182
  case "cancel":
16122
- return `${i}${import_picocolors2.default.gray(a)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(r2))}
16183
+ return `${i}${import_picocolors2.default.gray(a)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(r3))}
16123
16184
  ${import_picocolors2.default.gray(a)}`;
16124
16185
  default:
16125
16186
  return `${i}${import_picocolors2.default.cyan(a)} ${this.value ? `${import_picocolors2.default.green(j2)} ${n}` : `${import_picocolors2.default.dim(R2)} ${import_picocolors2.default.dim(n)}`} ${import_picocolors2.default.dim("/")} ${this.value ? `${import_picocolors2.default.dim(R2)} ${import_picocolors2.default.dim(t2)}` : `${import_picocolors2.default.green(j2)} ${t2}`}
@@ -16130,16 +16191,16 @@ ${import_picocolors2.default.cyan(m2)}
16130
16191
  };
16131
16192
  de = (s) => {
16132
16193
  const n = (t2, i) => {
16133
- const r2 = t2.label ?? String(t2.value);
16194
+ const r3 = t2.label ?? String(t2.value);
16134
16195
  switch (i) {
16135
16196
  case "selected":
16136
- return `${import_picocolors2.default.dim(r2)}`;
16197
+ return `${import_picocolors2.default.dim(r3)}`;
16137
16198
  case "active":
16138
- return `${import_picocolors2.default.green(j2)} ${r2} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}`;
16199
+ return `${import_picocolors2.default.green(j2)} ${r3} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}`;
16139
16200
  case "cancelled":
16140
- return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(r2))}`;
16201
+ return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(r3))}`;
16141
16202
  default:
16142
- return `${import_picocolors2.default.dim(R2)} ${import_picocolors2.default.dim(r2)}`;
16203
+ return `${import_picocolors2.default.dim(R2)} ${import_picocolors2.default.dim(r3)}`;
16143
16204
  }
16144
16205
  };
16145
16206
  return new jD({ options: s.options, initialValue: s.initialValue, render() {
@@ -16153,7 +16214,7 @@ ${y2(this.state)} ${s.message}
16153
16214
  return `${t2}${import_picocolors2.default.gray(a)} ${n(this.options[this.cursor], "cancelled")}
16154
16215
  ${import_picocolors2.default.gray(a)}`;
16155
16216
  default:
16156
- return `${t2}${import_picocolors2.default.cyan(a)} ${k2({ cursor: this.cursor, options: this.options, maxItems: s.maxItems, style: (i, r2) => n(i, r2 ? "active" : "inactive") }).join(`
16217
+ return `${t2}${import_picocolors2.default.cyan(a)} ${k2({ cursor: this.cursor, options: this.options, maxItems: s.maxItems, style: (i, r3) => n(i, r3 ? "active" : "inactive") }).join(`
16157
16218
  ${import_picocolors2.default.cyan(a)} `)}
16158
16219
  ${import_picocolors2.default.cyan(m2)}
16159
16220
  `;
@@ -16162,8 +16223,8 @@ ${import_picocolors2.default.cyan(m2)}
16162
16223
  };
16163
16224
  he = (s) => {
16164
16225
  const n = (t2, i = "inactive") => {
16165
- const r2 = t2.label ?? String(t2.value);
16166
- return i === "selected" ? `${import_picocolors2.default.dim(r2)}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(r2))}` : i === "active" ? `${import_picocolors2.default.bgCyan(import_picocolors2.default.gray(` ${t2.value} `))} ${r2} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}` : `${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(` ${t2.value} `)))} ${r2} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}`;
16226
+ const r3 = t2.label ?? String(t2.value);
16227
+ return i === "selected" ? `${import_picocolors2.default.dim(r3)}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(r3))}` : i === "active" ? `${import_picocolors2.default.bgCyan(import_picocolors2.default.gray(` ${t2.value} `))} ${r3} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}` : `${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(` ${t2.value} `)))} ${r3} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}`;
16167
16228
  };
16168
16229
  return new OD({ options: s.options, initialValue: s.initialValue, render() {
16169
16230
  const t2 = `${import_picocolors2.default.gray(a)}
@@ -16176,7 +16237,7 @@ ${y2(this.state)} ${s.message}
16176
16237
  return `${t2}${import_picocolors2.default.gray(a)} ${n(this.options[0], "cancelled")}
16177
16238
  ${import_picocolors2.default.gray(a)}`;
16178
16239
  default:
16179
- return `${t2}${import_picocolors2.default.cyan(a)} ${this.options.map((i, r2) => n(i, r2 === this.cursor ? "active" : "inactive")).join(`
16240
+ return `${t2}${import_picocolors2.default.cyan(a)} ${this.options.map((i, r3) => n(i, r3 === this.cursor ? "active" : "inactive")).join(`
16180
16241
  ${import_picocolors2.default.cyan(a)} `)}
16181
16242
  ${import_picocolors2.default.cyan(m2)}
16182
16243
  `;
@@ -16185,8 +16246,8 @@ ${import_picocolors2.default.cyan(m2)}
16185
16246
  };
16186
16247
  pe = (s) => {
16187
16248
  const n = (t2, i) => {
16188
- const r2 = t2.label ?? String(t2.value);
16189
- return i === "active" ? `${import_picocolors2.default.cyan(V2)} ${r2} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}` : i === "selected" ? `${import_picocolors2.default.green(M2)} ${import_picocolors2.default.dim(r2)}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(r2))}` : i === "active-selected" ? `${import_picocolors2.default.green(M2)} ${r2} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}` : i === "submitted" ? `${import_picocolors2.default.dim(r2)}` : `${import_picocolors2.default.dim(G2)} ${import_picocolors2.default.dim(r2)}`;
16249
+ const r3 = t2.label ?? String(t2.value);
16250
+ return i === "active" ? `${import_picocolors2.default.cyan(V2)} ${r3} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}` : i === "selected" ? `${import_picocolors2.default.green(M2)} ${import_picocolors2.default.dim(r3)}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(r3))}` : i === "active-selected" ? `${import_picocolors2.default.green(M2)} ${r3} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}` : i === "submitted" ? `${import_picocolors2.default.dim(r3)}` : `${import_picocolors2.default.dim(G2)} ${import_picocolors2.default.dim(r3)}`;
16190
16251
  };
16191
16252
  return new wD({ options: s.options, initialValues: s.initialValues, required: s.required ?? true, cursorAt: s.cursorAt, validate(t2) {
16192
16253
  if (this.required && t2.length === 0) return `Please select at least one option.
@@ -16194,25 +16255,25 @@ ${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${impor
16194
16255
  }, render() {
16195
16256
  const t2 = `${import_picocolors2.default.gray(a)}
16196
16257
  ${y2(this.state)} ${s.message}
16197
- `, i = (r2, c2) => {
16198
- const o = this.value.includes(r2.value);
16199
- return c2 && o ? n(r2, "active-selected") : o ? n(r2, "selected") : n(r2, c2 ? "active" : "inactive");
16258
+ `, i = (r3, c2) => {
16259
+ const o = this.value.includes(r3.value);
16260
+ return c2 && o ? n(r3, "active-selected") : o ? n(r3, "selected") : n(r3, c2 ? "active" : "inactive");
16200
16261
  };
16201
16262
  switch (this.state) {
16202
16263
  case "submit":
16203
- return `${t2}${import_picocolors2.default.gray(a)} ${this.options.filter(({ value: r2 }) => this.value.includes(r2)).map((r2) => n(r2, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none")}`;
16264
+ return `${t2}${import_picocolors2.default.gray(a)} ${this.options.filter(({ value: r3 }) => this.value.includes(r3)).map((r3) => n(r3, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none")}`;
16204
16265
  case "cancel": {
16205
- const r2 = this.options.filter(({ value: c2 }) => this.value.includes(c2)).map((c2) => n(c2, "cancelled")).join(import_picocolors2.default.dim(", "));
16206
- return `${t2}${import_picocolors2.default.gray(a)} ${r2.trim() ? `${r2}
16266
+ const r3 = this.options.filter(({ value: c2 }) => this.value.includes(c2)).map((c2) => n(c2, "cancelled")).join(import_picocolors2.default.dim(", "));
16267
+ return `${t2}${import_picocolors2.default.gray(a)} ${r3.trim() ? `${r3}
16207
16268
  ${import_picocolors2.default.gray(a)}` : ""}`;
16208
16269
  }
16209
16270
  case "error": {
16210
- const r2 = this.error.split(`
16271
+ const r3 = this.error.split(`
16211
16272
  `).map((c2, o) => o === 0 ? `${import_picocolors2.default.yellow(m2)} ${import_picocolors2.default.yellow(c2)}` : ` ${c2}`).join(`
16212
16273
  `);
16213
16274
  return `${t2 + import_picocolors2.default.yellow(a)} ${k2({ options: this.options, cursor: this.cursor, maxItems: s.maxItems, style: i }).join(`
16214
16275
  ${import_picocolors2.default.yellow(a)} `)}
16215
- ${r2}
16276
+ ${r3}
16216
16277
  `;
16217
16278
  }
16218
16279
  default:
@@ -16224,8 +16285,8 @@ ${import_picocolors2.default.cyan(m2)}
16224
16285
  } }).prompt();
16225
16286
  };
16226
16287
  ge = (s) => {
16227
- const n = (t2, i, r2 = []) => {
16228
- const c2 = t2.label ?? String(t2.value), o = typeof t2.group == "string", l2 = o && (r2[r2.indexOf(t2) + 1] ?? { group: true }), $2 = o && l2.group === true, d2 = o ? `${$2 ? m2 : a} ` : "";
16288
+ const n = (t2, i, r3 = []) => {
16289
+ const c2 = t2.label ?? String(t2.value), o = typeof t2.group == "string", l2 = o && (r3[r3.indexOf(t2) + 1] ?? { group: true }), $2 = o && l2.group === true, d2 = o ? `${$2 ? m2 : a} ` : "";
16229
16290
  return i === "active" ? `${import_picocolors2.default.dim(d2)}${import_picocolors2.default.cyan(V2)} ${c2} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}` : i === "group-active" ? `${d2}${import_picocolors2.default.cyan(V2)} ${import_picocolors2.default.dim(c2)}` : i === "group-active-selected" ? `${d2}${import_picocolors2.default.green(M2)} ${import_picocolors2.default.dim(c2)}` : i === "selected" ? `${import_picocolors2.default.dim(d2)}${import_picocolors2.default.green(M2)} ${import_picocolors2.default.dim(c2)}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(c2))}` : i === "active-selected" ? `${import_picocolors2.default.dim(d2)}${import_picocolors2.default.green(M2)} ${c2} ${t2.hint ? import_picocolors2.default.dim(`(${t2.hint})`) : ""}` : i === "submitted" ? `${import_picocolors2.default.dim(c2)}` : `${import_picocolors2.default.dim(d2)}${import_picocolors2.default.dim(G2)} ${import_picocolors2.default.dim(c2)}`;
16230
16291
  };
16231
16292
  return new dD({ options: s.options, initialValues: s.initialValues, required: s.required ?? true, cursorAt: s.cursorAt, validate(t2) {
@@ -16239,25 +16300,25 @@ ${y2(this.state)} ${s.message}
16239
16300
  case "submit":
16240
16301
  return `${t2}${import_picocolors2.default.gray(a)} ${this.options.filter(({ value: i }) => this.value.includes(i)).map((i) => n(i, "submitted")).join(import_picocolors2.default.dim(", "))}`;
16241
16302
  case "cancel": {
16242
- const i = this.options.filter(({ value: r2 }) => this.value.includes(r2)).map((r2) => n(r2, "cancelled")).join(import_picocolors2.default.dim(", "));
16303
+ const i = this.options.filter(({ value: r3 }) => this.value.includes(r3)).map((r3) => n(r3, "cancelled")).join(import_picocolors2.default.dim(", "));
16243
16304
  return `${t2}${import_picocolors2.default.gray(a)} ${i.trim() ? `${i}
16244
16305
  ${import_picocolors2.default.gray(a)}` : ""}`;
16245
16306
  }
16246
16307
  case "error": {
16247
16308
  const i = this.error.split(`
16248
- `).map((r2, c2) => c2 === 0 ? `${import_picocolors2.default.yellow(m2)} ${import_picocolors2.default.yellow(r2)}` : ` ${r2}`).join(`
16309
+ `).map((r3, c2) => c2 === 0 ? `${import_picocolors2.default.yellow(m2)} ${import_picocolors2.default.yellow(r3)}` : ` ${r3}`).join(`
16249
16310
  `);
16250
- return `${t2}${import_picocolors2.default.yellow(a)} ${this.options.map((r2, c2, o) => {
16251
- const l2 = this.value.includes(r2.value) || r2.group === true && this.isGroupSelected(`${r2.value}`), $2 = c2 === this.cursor;
16252
- return !$2 && typeof r2.group == "string" && this.options[this.cursor].value === r2.group ? n(r2, l2 ? "group-active-selected" : "group-active", o) : $2 && l2 ? n(r2, "active-selected", o) : l2 ? n(r2, "selected", o) : n(r2, $2 ? "active" : "inactive", o);
16311
+ return `${t2}${import_picocolors2.default.yellow(a)} ${this.options.map((r3, c2, o) => {
16312
+ const l2 = this.value.includes(r3.value) || r3.group === true && this.isGroupSelected(`${r3.value}`), $2 = c2 === this.cursor;
16313
+ return !$2 && typeof r3.group == "string" && this.options[this.cursor].value === r3.group ? n(r3, l2 ? "group-active-selected" : "group-active", o) : $2 && l2 ? n(r3, "active-selected", o) : l2 ? n(r3, "selected", o) : n(r3, $2 ? "active" : "inactive", o);
16253
16314
  }).join(`
16254
16315
  ${import_picocolors2.default.yellow(a)} `)}
16255
16316
  ${i}
16256
16317
  `;
16257
16318
  }
16258
16319
  default:
16259
- return `${t2}${import_picocolors2.default.cyan(a)} ${this.options.map((i, r2, c2) => {
16260
- const o = this.value.includes(i.value) || i.group === true && this.isGroupSelected(`${i.value}`), l2 = r2 === this.cursor;
16320
+ return `${t2}${import_picocolors2.default.cyan(a)} ${this.options.map((i, r3, c2) => {
16321
+ const o = this.value.includes(i.value) || i.group === true && this.isGroupSelected(`${i.value}`), l2 = r3 === this.cursor;
16261
16322
  return !l2 && typeof i.group == "string" && this.options[this.cursor].value === i.group ? n(i, o ? "group-active-selected" : "group-active", c2) : l2 && o ? n(i, "active-selected", c2) : o ? n(i, "selected", c2) : n(i, l2 ? "active" : "inactive", c2);
16262
16323
  }).join(`
16263
16324
  ${import_picocolors2.default.cyan(a)} `)}
@@ -16270,15 +16331,15 @@ ${import_picocolors2.default.cyan(m2)}
16270
16331
  const t2 = `
16271
16332
  ${s}
16272
16333
  `.split(`
16273
- `), i = T2(n).length, r2 = Math.max(t2.reduce((o, l2) => {
16334
+ `), i = T2(n).length, r3 = Math.max(t2.reduce((o, l2) => {
16274
16335
  const $2 = T2(l2);
16275
16336
  return $2.length > o ? $2.length : o;
16276
- }, 0), i) + 2, c2 = t2.map((o) => `${import_picocolors2.default.gray(a)} ${import_picocolors2.default.dim(o)}${" ".repeat(r2 - T2(o).length)}${import_picocolors2.default.gray(a)}`).join(`
16337
+ }, 0), i) + 2, c2 = t2.map((o) => `${import_picocolors2.default.gray(a)} ${import_picocolors2.default.dim(o)}${" ".repeat(r3 - T2(o).length)}${import_picocolors2.default.gray(a)}`).join(`
16277
16338
  `);
16278
16339
  process.stdout.write(`${import_picocolors2.default.gray(a)}
16279
- ${import_picocolors2.default.green(S2)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(N2.repeat(Math.max(r2 - i - 1, 1)) + re)}
16340
+ ${import_picocolors2.default.green(S2)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(N2.repeat(Math.max(r3 - i - 1, 1)) + re)}
16280
16341
  ${c2}
16281
- ${import_picocolors2.default.gray(ie + N2.repeat(r2 + 2) + ne)}
16342
+ ${import_picocolors2.default.gray(ie + N2.repeat(r3 + 2) + ne)}
16282
16343
  `);
16283
16344
  };
16284
16345
  ve = (s = "") => {
@@ -16299,9 +16360,9 @@ ${import_picocolors2.default.gray(m2)} ${s}
16299
16360
  v2 = { message: (s = "", { symbol: n = import_picocolors2.default.gray(a) } = {}) => {
16300
16361
  const t2 = [`${import_picocolors2.default.gray(a)}`];
16301
16362
  if (s) {
16302
- const [i, ...r2] = s.split(`
16363
+ const [i, ...r3] = s.split(`
16303
16364
  `);
16304
- t2.push(`${n} ${i}`, ...r2.map((c2) => `${import_picocolors2.default.gray(a)} ${c2}`));
16365
+ t2.push(`${n} ${i}`, ...r3.map((c2) => `${import_picocolors2.default.gray(a)} ${c2}`));
16305
16366
  }
16306
16367
  process.stdout.write(`${t2.join(`
16307
16368
  `)}
@@ -16321,7 +16382,7 @@ ${import_picocolors2.default.gray(m2)} ${s}
16321
16382
  } };
16322
16383
  L2 = () => {
16323
16384
  const s = E ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], n = E ? 80 : 120, t2 = process.env.CI === "true";
16324
- let i, r2, c2 = false, o = "", l2;
16385
+ let i, r3, c2 = false, o = "", l2;
16325
16386
  const $2 = (h2) => {
16326
16387
  const g2 = h2 > 1 ? "Something went wrong" : "Canceled";
16327
16388
  c2 && P2(g2, h2);
@@ -16340,14 +16401,14 @@ ${import_picocolors2.default.gray(m2)} ${s}
16340
16401
  c2 = true, i = cD(), o = x2(h2), process.stdout.write(`${import_picocolors2.default.gray(a)}
16341
16402
  `);
16342
16403
  let g2 = 0, f2 = 0;
16343
- b2(), r2 = setInterval(() => {
16404
+ b2(), r3 = setInterval(() => {
16344
16405
  if (t2 && o === l2) return;
16345
16406
  I2(), l2 = o;
16346
16407
  const W2 = import_picocolors2.default.magenta(s[g2]), _2 = t2 ? "..." : ".".repeat(Math.floor(f2)).slice(0, 3);
16347
16408
  process.stdout.write(`${W2} ${o}${_2}`), g2 = g2 + 1 < s.length ? g2 + 1 : 0, f2 = f2 < s.length ? f2 + 0.125 : 0;
16348
16409
  }, n);
16349
16410
  }, P2 = (h2 = "", g2 = 0) => {
16350
- c2 = false, clearInterval(r2), I2();
16411
+ c2 = false, clearInterval(r3), I2();
16351
16412
  const f2 = g2 === 0 ? import_picocolors2.default.green(S2) : g2 === 1 ? import_picocolors2.default.red(A2) : import_picocolors2.default.red(B);
16352
16413
  o = x2(h2 ?? o), process.stdout.write(`${f2} ${o}
16353
16414
  `), C(), i();
@@ -16358,15 +16419,15 @@ ${import_picocolors2.default.gray(m2)} ${s}
16358
16419
  };
16359
16420
  be = async (s, n) => {
16360
16421
  const t2 = {}, i = Object.keys(s);
16361
- for (const r2 of i) {
16362
- const c2 = s[r2], o = await c2({ results: t2 })?.catch((l2) => {
16422
+ for (const r3 of i) {
16423
+ const c2 = s[r3], o = await c2({ results: t2 })?.catch((l2) => {
16363
16424
  throw l2;
16364
16425
  });
16365
16426
  if (typeof n?.onCancel == "function" && BD(o)) {
16366
- t2[r2] = "canceled", n.onCancel({ results: t2 });
16427
+ t2[r3] = "canceled", n.onCancel({ results: t2 });
16367
16428
  continue;
16368
16429
  }
16369
- t2[r2] = o;
16430
+ t2[r3] = o;
16370
16431
  }
16371
16432
  return t2;
16372
16433
  };
@@ -16901,10 +16962,10 @@ function validateWrittenConfig(configPath, projectRoot, checkPaths = true) {
16901
16962
  }
16902
16963
  const mRoots = cfg.paths.monorepo_roots;
16903
16964
  if (Array.isArray(mRoots)) {
16904
- for (const r2 of mRoots) {
16905
- if (typeof r2 !== "string" || r2 === ".") continue;
16906
- if (!existsSync21(resolve12(projectRoot, r2))) {
16907
- return `paths.monorepo_roots '${r2}' does not exist on disk`;
16965
+ for (const r3 of mRoots) {
16966
+ if (typeof r3 !== "string" || r3 === ".") continue;
16967
+ if (!existsSync21(resolve12(projectRoot, r3))) {
16968
+ return `paths.monorepo_roots '${r3}' does not exist on disk`;
16908
16969
  }
16909
16970
  }
16910
16971
  }
@@ -17077,6 +17138,22 @@ fi
17077
17138
  MASSU_VERSION="$1"
17078
17139
  shift
17079
17140
  MASSU_CLI="\${HOME}/.massu/runtime/\${MASSU_VERSION}/node_modules/@massu/core/dist/cli.js"
17141
+ # A DECIDING hook's exit code is its verdict: 0 allows, 2 denies. If the launch itself fails
17142
+ # (a runtime that cannot load exits 1), Claude Code would RUN the call, so for these hooks
17143
+ # any other status becomes a deny. Every other subcommand keeps the exec path below.
17144
+ if [ "\${1:-}" = hook-runner ]; then
17145
+ case "\${2:-}" in
17146
+ ${DECISION_HOOKS.join("|")})
17147
+ if [ -f "$MASSU_CLI" ]; then node "$MASSU_CLI" "$@"; else npx -y "@massu/core@\${MASSU_VERSION}" "$@"; fi
17148
+ st=$?
17149
+ if [ "$st" -ne 0 ] && [ "$st" -ne 2 ]; then
17150
+ echo "MASSU SECURITY GATE \u2014 BLOCKED: the $2 hook ended without a verdict (exit $st)" >&2
17151
+ exit 2
17152
+ fi
17153
+ exit "$st"
17154
+ ;;
17155
+ esac
17156
+ fi
17080
17157
  if [ -f "$MASSU_CLI" ]; then
17081
17158
  exec node "$MASSU_CLI" "$@"
17082
17159
  fi
@@ -17121,13 +17198,19 @@ function hookCmd(version, hookName, shim) {
17121
17198
  function getHookTimeout(name) {
17122
17199
  return HOOK_TIMEOUTS[name] ?? 5;
17123
17200
  }
17201
+ function hookEntry(version, name, shim) {
17202
+ const entry = { type: "command", command: hookCmd(version, name, shim), timeout: getHookTimeout(name) };
17203
+ if (BACKGROUND_HOOKS.has(name)) entry.async = true;
17204
+ if (DECISION_HOOKS.includes(name)) entry.onFailure = "block";
17205
+ return entry;
17206
+ }
17124
17207
  function buildHooksConfig(_hooksDir, shim = resolveMassuShim()) {
17125
17208
  const version = getInstallerVersion();
17126
17209
  return {
17127
17210
  SessionStart: [
17128
17211
  {
17129
17212
  hooks: [
17130
- { type: "command", command: hookCmd(version, "session-start", shim), timeout: getHookTimeout("session-start") }
17213
+ hookEntry(version, "session-start", shim)
17131
17214
  ]
17132
17215
  }
17133
17216
  ],
@@ -17142,26 +17225,26 @@ function buildHooksConfig(_hooksDir, shim = resolveMassuShim()) {
17142
17225
  {
17143
17226
  matcher: "Bash|Write|Edit",
17144
17227
  hooks: [
17145
- { type: "command", command: hookCmd(version, "pre-tool-use-gate", shim), timeout: getHookTimeout("pre-tool-use-gate") }
17228
+ hookEntry(version, "pre-tool-use-gate", shim)
17146
17229
  ]
17147
17230
  }
17148
17231
  ],
17149
17232
  PostToolUse: [
17150
17233
  {
17151
17234
  hooks: [
17152
- { type: "command", command: hookCmd(version, "post-tool-use", shim), timeout: getHookTimeout("post-tool-use") },
17153
- { type: "command", command: hookCmd(version, "quality-event", shim), timeout: getHookTimeout("quality-event") },
17154
- { type: "command", command: hookCmd(version, "cost-tracker", shim), timeout: getHookTimeout("cost-tracker") }
17235
+ hookEntry(version, "post-tool-use", shim),
17236
+ hookEntry(version, "quality-event", shim),
17237
+ hookEntry(version, "cost-tracker", shim)
17155
17238
  ]
17156
17239
  },
17157
17240
  {
17158
17241
  matcher: "Edit|Write",
17159
17242
  hooks: [
17160
- { type: "command", command: hookCmd(version, "post-edit-context", shim), timeout: getHookTimeout("post-edit-context") },
17243
+ hookEntry(version, "post-edit-context", shim),
17161
17244
  // Auto-learning pipeline — classifies failures and detects fixes on
17162
17245
  // file changes. See Phase 5-6 of the autodetect plan.
17163
- { type: "command", command: hookCmd(version, "fix-detector", shim), timeout: getHookTimeout("fix-detector") },
17164
- { type: "command", command: hookCmd(version, "classify-failure", shim), timeout: getHookTimeout("classify-failure") }
17246
+ hookEntry(version, "fix-detector", shim),
17247
+ hookEntry(version, "classify-failure", shim)
17165
17248
  ]
17166
17249
  },
17167
17250
  {
@@ -17169,35 +17252,35 @@ function buildHooksConfig(_hooksDir, shim = resolveMassuShim()) {
17169
17252
  hooks: [
17170
17253
  // Incident + rule enforcement pipelines fire on Write-only (incidents
17171
17254
  // are authored as .md files; rules are enforced after new-file drops).
17172
- { type: "command", command: hookCmd(version, "incident-pipeline", shim), timeout: getHookTimeout("incident-pipeline") },
17173
- { type: "command", command: hookCmd(version, "rule-enforcement-pipeline", shim), timeout: getHookTimeout("rule-enforcement-pipeline") }
17255
+ hookEntry(version, "incident-pipeline", shim),
17256
+ hookEntry(version, "rule-enforcement-pipeline", shim)
17174
17257
  ]
17175
17258
  }
17176
17259
  ],
17177
17260
  Stop: [
17178
17261
  {
17179
17262
  hooks: [
17180
- { type: "command", command: hookCmd(version, "session-end", shim), timeout: getHookTimeout("session-end") },
17263
+ hookEntry(version, "session-end", shim),
17181
17264
  // Session-end auto-learning aggregation (failure-class roll-up).
17182
- { type: "command", command: hookCmd(version, "auto-learning-pipeline", shim), timeout: getHookTimeout("auto-learning-pipeline") }
17265
+ hookEntry(version, "auto-learning-pipeline", shim)
17183
17266
  ]
17184
17267
  }
17185
17268
  ],
17186
17269
  PreCompact: [
17187
17270
  {
17188
17271
  hooks: [
17189
- { type: "command", command: hookCmd(version, "pre-compact", shim), timeout: getHookTimeout("pre-compact") }
17272
+ hookEntry(version, "pre-compact", shim)
17190
17273
  ]
17191
17274
  }
17192
17275
  ],
17193
17276
  UserPromptSubmit: [
17194
17277
  {
17195
17278
  hooks: [
17196
- { type: "command", command: hookCmd(version, "user-prompt", shim), timeout: getHookTimeout("user-prompt") },
17197
- { type: "command", command: hookCmd(version, "intent-suggester", shim), timeout: getHookTimeout("intent-suggester") },
17279
+ hookEntry(version, "user-prompt", shim),
17280
+ hookEntry(version, "intent-suggester", shim),
17198
17281
  // plan-living-memory-slice-1 P3-002: automatic relevant-recall.
17199
17282
  // Injects a compact "🧠 Relevant memory" block; fails open (empty).
17200
- { type: "command", command: hookCmd(version, "memory-recall", shim), timeout: getHookTimeout("memory-recall") }
17283
+ hookEntry(version, "memory-recall", shim)
17201
17284
  ]
17202
17285
  }
17203
17286
  ]
@@ -17675,6 +17758,7 @@ var init_init = __esm({
17675
17758
  init_settings_local();
17676
17759
  init_memory_path();
17677
17760
  init_hook_timeouts();
17761
+ init_hook_registry();
17678
17762
  init_detect();
17679
17763
  init_drift();
17680
17764
  __filename2 = fileURLToPath3(import.meta.url);
@@ -18261,41 +18345,6 @@ var init_license = __esm({
18261
18345
  }
18262
18346
  });
18263
18347
 
18264
- // src/lib/hook-registry.ts
18265
- function getExpectedHookFiles() {
18266
- return REGISTERED_HOOKS.map((name) => `${name}.js`);
18267
- }
18268
- var REGISTERED_HOOKS;
18269
- var init_hook_registry = __esm({
18270
- "src/lib/hook-registry.ts"() {
18271
- "use strict";
18272
- REGISTERED_HOOKS = [
18273
- "auto-learning-pipeline",
18274
- "classify-failure",
18275
- "cost-tracker",
18276
- "fix-detector",
18277
- "incident-pipeline",
18278
- "intent-suggester",
18279
- "memory-recall",
18280
- "post-edit-context",
18281
- "post-tool-use",
18282
- "pre-compact",
18283
- // P-E-019 (1.12.0): security-gate + pre-delete-check are kept registered
18284
- // for back-compat (operator settings.json may still reference them
18285
- // individually), but new installs emit only `pre-tool-use-gate` —
18286
- // the consolidated single-spawn PreToolUse hook.
18287
- "pre-delete-check",
18288
- "pre-tool-use-gate",
18289
- "quality-event",
18290
- "rule-enforcement-pipeline",
18291
- "security-gate",
18292
- "session-end",
18293
- "session-start",
18294
- "user-prompt"
18295
- ];
18296
- }
18297
- });
18298
-
18299
18348
  // src/preflight.ts
18300
18349
  import { existsSync as existsSync22 } from "fs";
18301
18350
  function assertCodegraphUsable(dbPath) {
@@ -18382,6 +18431,7 @@ var init_preflight = __esm({
18382
18431
  var hook_runner_exports = {};
18383
18432
  __export(hook_runner_exports, {
18384
18433
  HOOK_NAME_TO_FILE: () => HOOK_NAME_TO_FILE,
18434
+ decisionExitCode: () => decisionExitCode,
18385
18435
  resolveHookFile: () => resolveHookFile,
18386
18436
  runHookRunner: () => runHookRunner
18387
18437
  });
@@ -18413,6 +18463,16 @@ function resolveHookFile(hookName) {
18413
18463
  `Hook file not found for "${hookName}". Searched: ${candidates.join(", ")}. This indicates a broken @massu/core install. Re-run \`npx -y @massu/core init\`.`
18414
18464
  );
18415
18465
  }
18466
+ function decisionExitCode(hookName, code) {
18467
+ if (!DECISION_HOOKS.includes(hookName) || code === 0 || code === 2) return code;
18468
+ process.stderr.write(
18469
+ `MASSU SECURITY GATE \u2014 BLOCKED
18470
+
18471
+ The ${hookName} hook ended without a verdict (exit ${code}). A gate that did not decide has not approved this call.
18472
+ `
18473
+ );
18474
+ return 2;
18475
+ }
18416
18476
  async function runHookRunner(args2) {
18417
18477
  const hookName = args2[0];
18418
18478
  if (!hookName) {
@@ -18441,11 +18501,7 @@ Recognized: ${Object.keys(HOOK_NAME_TO_FILE).join(", ")}
18441
18501
  env: { ...process.env, MASSU_HOOK_RUNTIME: "1" }
18442
18502
  });
18443
18503
  child.on("exit", (code, signal) => {
18444
- if (signal) {
18445
- resolvePromise({ exitCode: 128 });
18446
- return;
18447
- }
18448
- resolvePromise({ exitCode: code ?? 0 });
18504
+ resolvePromise({ exitCode: decisionExitCode(hookName, signal ? 128 : code ?? 0) });
18449
18505
  });
18450
18506
  child.on("error", (err) => {
18451
18507
  process.stderr.write(`massu hook-runner: failed to spawn hook "${hookName}": ${err.message}
@@ -20165,7 +20221,7 @@ async function embedMissingChunks(db, opts = {}) {
20165
20221
  )
20166
20222
  ORDER BY c.id LIMIT ?`
20167
20223
  ).all(cursor, batchSize);
20168
- return rows.map((r2) => ({ id: r2.id, text: chunkEmbedText(r2.heading, r2.content) }));
20224
+ return rows.map((r3) => ({ id: r3.id, text: chunkEmbedText(r3.heading, r3.content) }));
20169
20225
  }
20170
20226
  },
20171
20227
  opts
@@ -21285,7 +21341,7 @@ function buildIndexLines(db, memoryDir, planned, config) {
21285
21341
  ORDER BY o.importance DESC, o.created_at_epoch DESC
21286
21342
  LIMIT ?`
21287
21343
  ).all(config.indexMaxLines + planned.length);
21288
- const byPath = new Map(rows.map((r2) => [r2.rel_path.toLowerCase(), r2]));
21344
+ const byPath = new Map(rows.map((r3) => [r3.rel_path.toLowerCase(), r3]));
21289
21345
  for (const p19 of planned) {
21290
21346
  byPath.set(p19.relPath.toLowerCase(), {
21291
21347
  rel_path: p19.relPath,
@@ -21294,11 +21350,11 @@ function buildIndexLines(db, memoryDir, planned, config) {
21294
21350
  created_at_epoch: 0
21295
21351
  });
21296
21352
  }
21297
- const live = [...byPath.values()].filter((r2) => !isTombstoned(memoryDir, r2.rel_path));
21353
+ const live = [...byPath.values()].filter((r3) => !isTombstoned(memoryDir, r3.rel_path));
21298
21354
  const ranked = live.sort(
21299
21355
  (a2, b2) => b2.importance - a2.importance || b2.created_at_epoch - a2.created_at_epoch
21300
21356
  );
21301
- return ranked.slice(0, config.indexMaxLines).map((r2) => `- [${oneLine(r2.name)}](${r2.rel_path}) \u2014 learned by Massu`);
21357
+ return ranked.slice(0, config.indexMaxLines).map((r3) => `- [${oneLine(r3.name)}](${r3.rel_path}) \u2014 learned by Massu`);
21302
21358
  }
21303
21359
  function oneLine(s) {
21304
21360
  return s.replace(/[\r\n]+/g, " ").slice(0, 120);
@@ -21494,14 +21550,14 @@ function memoryRestore(memoryDir, opts = {}) {
21494
21550
  return { exitCode: 1, output: "No backups exist yet. Nothing to restore." };
21495
21551
  }
21496
21552
  try {
21497
- const r2 = restoreBackup(memoryDir, { ...opts, home });
21553
+ const r3 = restoreBackup(memoryDir, { ...opts, home });
21498
21554
  const lines = [
21499
- r2.dryRun ? `--dry-run: would restore ${r2.wouldRestore.length} file(s) from ${r2.stamp}. Nothing changed.` : `Restored ${r2.filesRestored} file(s) from ${r2.stamp}.`
21555
+ r3.dryRun ? `--dry-run: would restore ${r3.wouldRestore.length} file(s) from ${r3.stamp}. Nothing changed.` : `Restored ${r3.filesRestored} file(s) from ${r3.stamp}.`
21500
21556
  ];
21501
- for (const f2 of r2.wouldRestore) lines.push(` ${f2}`);
21502
- if (r2.presentButNotInBackup.length > 0) {
21557
+ for (const f2 of r3.wouldRestore) lines.push(` ${f2}`);
21558
+ if (r3.presentButNotInBackup.length > 0) {
21503
21559
  lines.push("", "These files exist now but are NOT in the backup. Massu left them alone:");
21504
- for (const f2 of r2.presentButNotInBackup) lines.push(` ${f2}`);
21560
+ for (const f2 of r3.presentButNotInBackup) lines.push(` ${f2}`);
21505
21561
  }
21506
21562
  return { exitCode: 0, output: lines.join("\n") };
21507
21563
  } catch (err) {
@@ -21535,8 +21591,8 @@ function loadRenderCandidates(db) {
21535
21591
  ORDER BY importance DESC, created_at_epoch DESC
21536
21592
  LIMIT ${CANDIDATE_WINDOW}`
21537
21593
  ).all(cfg.renderMinImportance, ...RENDERABLE_MEMORY_TYPES);
21538
- const reingest = rows.filter((r2) => r2.title.startsWith("[memory-file]"));
21539
- const kept = rows.filter((r2) => !r2.title.startsWith("[memory-file]"));
21594
+ const reingest = rows.filter((r3) => r3.title.startsWith("[memory-file]"));
21595
+ const kept = rows.filter((r3) => !r3.title.startsWith("[memory-file]"));
21540
21596
  const excluded = reingest.length ? [{ reason: EXCLUSION_MEMORY_FILE_REINGEST, count: reingest.length }] : [];
21541
21597
  const ledger = {
21542
21598
  population,
@@ -21553,13 +21609,13 @@ function loadRenderCandidates(db) {
21553
21609
  }
21554
21610
  return {
21555
21611
  ledger,
21556
- candidates: kept.map((r2) => ({
21557
- observationId: r2.id,
21558
- name: r2.title,
21559
- title: r2.title,
21560
- body: r2.detail ?? "",
21561
- importance: r2.importance,
21562
- origin: r2.origin
21612
+ candidates: kept.map((r3) => ({
21613
+ observationId: r3.id,
21614
+ name: r3.title,
21615
+ title: r3.title,
21616
+ body: r3.detail ?? "",
21617
+ importance: r3.importance,
21618
+ origin: r3.origin
21563
21619
  }))
21564
21620
  };
21565
21621
  }
@@ -21742,8 +21798,8 @@ async function runMemoryRenderCli(sub, args2) {
21742
21798
  }
21743
21799
  if (result.refusals.length > 0) {
21744
21800
  lines.push("", `${result.refusals.length} memory/memories REFUSED:`);
21745
- for (const r2 of result.refusals) {
21746
- lines.push(` ${r2.name}: ${r2.reason}${r2.detail ? ` (${r2.detail})` : ""}`);
21801
+ for (const r3 of result.refusals) {
21802
+ lines.push(` ${r3.name}: ${r3.reason}${r3.detail ? ` (${r3.detail})` : ""}`);
21747
21803
  }
21748
21804
  }
21749
21805
  lines.push("", `Bytes written: ${result.bytesWritten} (a dry run always writes 0).`);
@@ -21765,7 +21821,7 @@ async function runMemoryRenderCli(sub, args2) {
21765
21821
  "",
21766
21822
  "Sample (first 10):"
21767
21823
  ];
21768
- for (const r2 of sampleNoise2(db, 10)) lines.push(` [${r2.reason}] id=${r2.id} ${r2.type}: ${r2.title.slice(0, 60)}`);
21824
+ for (const r3 of sampleNoise2(db, 10)) lines.push(` [${r3.reason}] id=${r3.id} ${r3.type}: ${r3.title.slice(0, 60)}`);
21769
21825
  if (apply) {
21770
21826
  const result = pruneNoiseObservations2(db, { dryRun: false });
21771
21827
  lines.push("", `Expired ${result.expired} row(s) (CR-61: expired, NOT deleted; still asOf-queryable).`);
@@ -21859,12 +21915,12 @@ function readReposRegistry(home = homedir13()) {
21859
21915
  try {
21860
21916
  const parsed = JSON.parse(readFileSync29(p19, "utf-8"));
21861
21917
  if (!parsed || typeof parsed !== "object") return emptyRegistry();
21862
- const r2 = parsed;
21863
- if (!Array.isArray(r2.repos)) return emptyRegistry();
21864
- const repos = r2.repos.filter(
21918
+ const r3 = parsed;
21919
+ if (!Array.isArray(r3.repos)) return emptyRegistry();
21920
+ const repos = r3.repos.filter(
21865
21921
  (e2) => !!e2 && typeof e2 === "object" && typeof e2.repo_id === "string" && typeof e2.label === "string"
21866
21922
  );
21867
- return { version: typeof r2.version === "number" ? r2.version : REPOS_REGISTRY_VERSION, repos };
21923
+ return { version: typeof r3.version === "number" ? r3.version : REPOS_REGISTRY_VERSION, repos };
21868
21924
  } catch {
21869
21925
  return emptyRegistry();
21870
21926
  }
@@ -22038,7 +22094,7 @@ function acceptSharedMemory(db, recordHash, opts = {}) {
22038
22094
  const verdict = verifyLocalShareEnvelope(env, pinned, home);
22039
22095
  if (verdict.kind !== "valid") return refuse(db, recordHash, `reverify_${verdict.kind}`);
22040
22096
  if (!hasSignedLoadBearingKeys(env)) return refuse(db, recordHash, "unsigned_field");
22041
- const signed = parseRecords(env.records_json).find((r2) => r2.record_hash === recordHash);
22097
+ const signed = parseRecords(env.records_json).find((r3) => r3.record_hash === recordHash);
22042
22098
  if (!signed) return refuse(db, recordHash, "not_in_signed_records");
22043
22099
  if (hashSharedMemoryRecord(stripHash(signed)) !== recordHash) {
22044
22100
  return refuse(db, recordHash, "record_hash_mismatch");
@@ -22134,20 +22190,20 @@ function listPendingSharedMemories(db, opts = {}) {
22134
22190
  LIMIT 500`
22135
22191
  ).all();
22136
22192
  const out = [];
22137
- for (const r2 of rows) {
22193
+ for (const r3 of rows) {
22138
22194
  let rec = null;
22139
22195
  let valid = false;
22140
22196
  try {
22141
- rec = JSON.parse(r2.record_json);
22142
- const env = JSON.parse(r2.envelope_raw);
22143
- const pinned = getSharedPin(db, r2.origin_repo_id);
22197
+ rec = JSON.parse(r3.record_json);
22198
+ const env = JSON.parse(r3.envelope_raw);
22199
+ const pinned = getSharedPin(db, r3.origin_repo_id);
22144
22200
  valid = !!pinned && verifyLocalShareEnvelope(env, pinned, home).kind === "valid";
22145
22201
  } catch {
22146
22202
  }
22147
22203
  out.push({
22148
- record_hash: r2.record_hash,
22149
- origin_repo_label: deriveRepoLabel(r2.origin_repo_label),
22150
- received_at_epoch: r2.received_at_epoch,
22204
+ record_hash: r3.record_hash,
22205
+ origin_repo_label: deriveRepoLabel(r3.origin_repo_label),
22206
+ received_at_epoch: r3.received_at_epoch,
22151
22207
  type: rec?.type ?? "unknown",
22152
22208
  title: sanitizeCrossRepoTitle(rec?.title ?? ""),
22153
22209
  detail: sanitizeCrossRepoBody(rec?.detail ?? ""),
@@ -22168,14 +22224,14 @@ function parseRecords(json) {
22168
22224
  return [];
22169
22225
  }
22170
22226
  }
22171
- function stripHash(r2) {
22227
+ function stripHash(r3) {
22172
22228
  return {
22173
- type: r2.type,
22174
- title: r2.title,
22175
- detail: r2.detail,
22176
- importance: r2.importance,
22177
- created_at_epoch: r2.created_at_epoch,
22178
- superseded_by_hash: r2.superseded_by_hash
22229
+ type: r3.type,
22230
+ title: r3.title,
22231
+ detail: r3.detail,
22232
+ importance: r3.importance,
22233
+ created_at_epoch: r3.created_at_epoch,
22234
+ superseded_by_hash: r3.superseded_by_hash
22179
22235
  };
22180
22236
  }
22181
22237
  function clampImportance(n) {
@@ -22902,40 +22958,40 @@ function validateCandidatePayload(raw) {
22902
22958
  if (!raw || typeof raw !== "object") {
22903
22959
  throw new CandidatePayloadValidationError("payload is not an object");
22904
22960
  }
22905
- const r2 = raw;
22906
- if (typeof r2.prompt !== "string" || r2.prompt.length === 0) {
22961
+ const r3 = raw;
22962
+ if (typeof r3.prompt !== "string" || r3.prompt.length === 0) {
22907
22963
  throw new CandidatePayloadValidationError("prompt must be a non-empty string");
22908
22964
  }
22909
- if (typeof r2.prompt_hash !== "string" || !/^[a-f0-9]{16}$/.test(r2.prompt_hash)) {
22965
+ if (typeof r3.prompt_hash !== "string" || !/^[a-f0-9]{16}$/.test(r3.prompt_hash)) {
22910
22966
  throw new CandidatePayloadValidationError("prompt_hash must be 16 hex chars");
22911
22967
  }
22912
- if (typeof r2.score !== "number" || r2.score < -200 || r2.score > 200 || !Number.isFinite(r2.score)) {
22968
+ if (typeof r3.score !== "number" || r3.score < -200 || r3.score > 200 || !Number.isFinite(r3.score)) {
22913
22969
  throw new CandidatePayloadValidationError("score must be a finite number in [-200, 200]");
22914
22970
  }
22915
- if (!Array.isArray(r2.signals)) {
22971
+ if (!Array.isArray(r3.signals)) {
22916
22972
  throw new CandidatePayloadValidationError("signals must be an array");
22917
22973
  }
22918
- for (const s of r2.signals) {
22974
+ for (const s of r3.signals) {
22919
22975
  if (!s || typeof s !== "object") throw new CandidatePayloadValidationError("signal entry not an object");
22920
22976
  const sig = s;
22921
22977
  if (typeof sig.name !== "string") throw new CandidatePayloadValidationError("signal.name must be a string");
22922
22978
  if (typeof sig.baseWeight !== "number") throw new CandidatePayloadValidationError("signal.baseWeight must be a number");
22923
22979
  if (typeof sig.applied !== "number") throw new CandidatePayloadValidationError("signal.applied must be a number");
22924
22980
  }
22925
- if (!Array.isArray(r2.prior_turn_files)) {
22981
+ if (!Array.isArray(r3.prior_turn_files)) {
22926
22982
  throw new CandidatePayloadValidationError("prior_turn_files must be an array");
22927
22983
  }
22928
- if (typeof r2.timestamp !== "string") {
22984
+ if (typeof r3.timestamp !== "string") {
22929
22985
  throw new CandidatePayloadValidationError("timestamp must be a string");
22930
22986
  }
22931
- if (typeof r2.session_id !== "string") {
22987
+ if (typeof r3.session_id !== "string") {
22932
22988
  throw new CandidatePayloadValidationError("session_id must be a string");
22933
22989
  }
22934
- if (r2.provenance !== void 0) {
22935
- if (!r2.provenance || typeof r2.provenance !== "object") {
22990
+ if (r3.provenance !== void 0) {
22991
+ if (!r3.provenance || typeof r3.provenance !== "object") {
22936
22992
  throw new CandidatePayloadValidationError("provenance must be an object when present");
22937
22993
  }
22938
- const p19 = r2.provenance;
22994
+ const p19 = r3.provenance;
22939
22995
  const origin = p19.origin;
22940
22996
  const isTeamOrPack = origin === "team" || origin === "pack";
22941
22997
  const isRepo = typeof origin === "string" && isCrossRepoOrigin(origin);
@@ -22960,17 +23016,17 @@ function validateCandidatePayload(raw) {
22960
23016
  if (p19.apply_mac !== void 0 && typeof p19.apply_mac !== "string") {
22961
23017
  throw new CandidatePayloadValidationError("provenance.apply_mac, when present, must be a string");
22962
23018
  }
22963
- if (!isRuleDestination(r2.destination)) {
23019
+ if (!isRuleDestination(r3.destination)) {
22964
23020
  throw new CandidatePayloadValidationError("provenance-bearing candidate must declare a valid destination");
22965
23021
  }
22966
- if (r2.draft_text !== void 0 && typeof r2.draft_text !== "string") {
23022
+ if (r3.draft_text !== void 0 && typeof r3.draft_text !== "string") {
22967
23023
  throw new CandidatePayloadValidationError("draft_text must be a string when present");
22968
23024
  }
22969
23025
  }
22970
- } else if (r2.destination !== void 0 && !isRuleDestination(r2.destination)) {
23026
+ } else if (r3.destination !== void 0 && !isRuleDestination(r3.destination)) {
22971
23027
  throw new CandidatePayloadValidationError("destination, when present, must be a valid RuleDestination");
22972
23028
  }
22973
- return r2;
23029
+ return r3;
22974
23030
  }
22975
23031
  async function applyRuleCandidate(db, opts) {
22976
23032
  const ent = await assertAutoLearningEntitled();
@@ -23325,8 +23381,8 @@ function dismissRuleCandidate(db, opts) {
23325
23381
  `);
23326
23382
  for (const sig of candidate.signals) {
23327
23383
  if (sig.baseWeight <= 0) continue;
23328
- const r2 = upsert.run(sig.name);
23329
- blacklistUpdates += r2.changes;
23384
+ const r3 = upsert.run(sig.name);
23385
+ blacklistUpdates += r3.changes;
23330
23386
  }
23331
23387
  const entry = {
23332
23388
  eventType: "rule_dismissed",
@@ -23758,27 +23814,27 @@ function validateRulePackRule(rule) {
23758
23814
  if (typeof rule !== "object" || rule === null || Array.isArray(rule)) {
23759
23815
  return { valid: false, errors: ["rule must be a non-null object"], requiresHardened: false };
23760
23816
  }
23761
- const r2 = rule;
23762
- if (!isNonEmptyString(r2.title)) {
23817
+ const r3 = rule;
23818
+ if (!isNonEmptyString(r3.title)) {
23763
23819
  errors.push("missing required field: title");
23764
23820
  }
23765
- if (!isNonEmptyString(r2.description)) {
23821
+ if (!isNonEmptyString(r3.description)) {
23766
23822
  errors.push("missing required field: description");
23767
23823
  }
23768
- if (!isNonEmptyString(r2.severity)) {
23824
+ if (!isNonEmptyString(r3.severity)) {
23769
23825
  errors.push("missing required field: severity");
23770
23826
  }
23771
- const destinationKnown = isRuleDestination2(r2.destination);
23827
+ const destinationKnown = isRuleDestination2(r3.destination);
23772
23828
  if (!destinationKnown) {
23773
23829
  errors.push(
23774
- `invalid destination: ${JSON.stringify(r2.destination)} is not a known RuleDestination (one of ${RULE_DESTINATIONS.join(", ")})`
23830
+ `invalid destination: ${JSON.stringify(r3.destination)} is not a known RuleDestination (one of ${RULE_DESTINATIONS.join(", ")})`
23775
23831
  );
23776
23832
  }
23777
23833
  if (destinationKnown) {
23778
- const destination = r2.destination;
23779
- const hasPattern = isNonEmptyString(r2.pattern);
23780
- const hasCheck = isNonEmptyString(r2.check);
23781
- const hasTextBody = isNonEmptyString(r2.description);
23834
+ const destination = r3.destination;
23835
+ const hasPattern = isNonEmptyString(r3.pattern);
23836
+ const hasCheck = isNonEmptyString(r3.check);
23837
+ const hasTextBody = isNonEmptyString(r3.description);
23782
23838
  switch (destination) {
23783
23839
  case "pattern-scanner":
23784
23840
  if (!hasPattern && !hasCheck) {
@@ -23804,7 +23860,7 @@ function validateRulePackRule(rule) {
23804
23860
  break;
23805
23861
  }
23806
23862
  }
23807
- const requiresHardened = destinationKnown && isExecutableDestination(r2.destination);
23863
+ const requiresHardened = destinationKnown && isExecutableDestination(r3.destination);
23808
23864
  return { valid: errors.length === 0, errors, requiresHardened };
23809
23865
  }
23810
23866
  var RULE_DESTINATIONS;
@@ -23916,8 +23972,8 @@ function countUntrusted(envelope) {
23916
23972
  }
23917
23973
  function isValidInstalledPack(p19) {
23918
23974
  if (!p19 || typeof p19 !== "object") return false;
23919
- const r2 = p19;
23920
- return typeof r2.slug === "string" && r2.slug.length > 0 && Array.isArray(r2.rules);
23975
+ const r3 = p19;
23976
+ return typeof r3.slug === "string" && r3.slug.length > 0 && Array.isArray(r3.rules);
23921
23977
  }
23922
23978
  function packRulePromptHash(slug, rule) {
23923
23979
  const { createHash: createHash17 } = __require("node:crypto");
@@ -24403,14 +24459,14 @@ function collectObservations(memDb, queryText, pool, nowMs, loadVec, modelId, di
24403
24459
  WHERE observations_fts MATCH ?${bm25Temporal ? ` AND ${bm25Temporal}` : ""}
24404
24460
  ORDER BY rank LIMIT ?`
24405
24461
  ).all(sanitizeFts5QueryOr(queryText), pool);
24406
- rows.forEach((r2, i) => {
24407
- map.set(r2.id, {
24408
- id: r2.id,
24462
+ rows.forEach((r3, i) => {
24463
+ map.set(r3.id, {
24464
+ id: r3.id,
24409
24465
  source: "observation",
24410
- title: r2.title,
24411
- snippet: snippetOf(r2.detail ?? r2.title),
24412
- importance: r2.importance,
24413
- ageDays: ageDaysFrom(r2.created_at_epoch * 1e3, nowMs),
24466
+ title: r3.title,
24467
+ snippet: snippetOf(r3.detail ?? r3.title),
24468
+ importance: r3.importance,
24469
+ ageDays: ageDaysFrom(r3.created_at_epoch * 1e3, nowMs),
24414
24470
  bm25Order: i,
24415
24471
  vecs: []
24416
24472
  });
@@ -24421,15 +24477,15 @@ function collectObservations(memDb, queryText, pool, nowMs, loadVec, modelId, di
24421
24477
  `SELECT id, title, detail, importance, created_at_epoch
24422
24478
  FROM observations${plainTemporal ? ` WHERE ${plainTemporal}` : ""} ORDER BY created_at_epoch DESC LIMIT ?`
24423
24479
  ).all(pool);
24424
- for (const r2 of recent) {
24425
- if (!map.has(r2.id)) {
24426
- map.set(r2.id, {
24427
- id: r2.id,
24480
+ for (const r3 of recent) {
24481
+ if (!map.has(r3.id)) {
24482
+ map.set(r3.id, {
24483
+ id: r3.id,
24428
24484
  source: "observation",
24429
- title: r2.title,
24430
- snippet: snippetOf(r2.detail ?? r2.title),
24431
- importance: r2.importance,
24432
- ageDays: ageDaysFrom(r2.created_at_epoch * 1e3, nowMs),
24485
+ title: r3.title,
24486
+ snippet: snippetOf(r3.detail ?? r3.title),
24487
+ importance: r3.importance,
24488
+ ageDays: ageDaysFrom(r3.created_at_epoch * 1e3, nowMs),
24433
24489
  bm25Order: null,
24434
24490
  vecs: []
24435
24491
  });
@@ -24469,14 +24525,14 @@ function collectLikeSource(db, source, cfg, queryText, pool, nowMs) {
24469
24525
  FROM ${cfg.table} WHERE (${likeClause})${cfg.temporalClause ? ` AND ${cfg.temporalClause}` : ""}
24470
24526
  ORDER BY ${cfg.createdCol} DESC LIMIT ?`
24471
24527
  ).all(...params, pool);
24472
- rows.forEach((r2, i) => {
24473
- map.set(r2.id, {
24474
- id: r2.id,
24528
+ rows.forEach((r3, i) => {
24529
+ map.set(r3.id, {
24530
+ id: r3.id,
24475
24531
  source,
24476
- title: r2.title,
24477
- snippet: snippetOf(r2.snippet ?? r2.title),
24532
+ title: r3.title,
24533
+ snippet: snippetOf(r3.snippet ?? r3.title),
24478
24534
  importance: 3,
24479
- ageDays: ageExpr(r2.created),
24535
+ ageDays: ageExpr(r3.created),
24480
24536
  bm25Order: i,
24481
24537
  vecs: []
24482
24538
  });
@@ -24489,15 +24545,15 @@ function collectLikeSource(db, source, cfg, queryText, pool, nowMs) {
24489
24545
  ${cfg.snippetCol} AS snippet, ${cfg.createdCol} AS created
24490
24546
  FROM ${cfg.table}${cfg.temporalClause ? ` WHERE ${cfg.temporalClause}` : ""} ORDER BY ${cfg.createdCol} DESC LIMIT ?`
24491
24547
  ).all(pool);
24492
- for (const r2 of recent) {
24493
- if (!map.has(r2.id)) {
24494
- map.set(r2.id, {
24495
- id: r2.id,
24548
+ for (const r3 of recent) {
24549
+ if (!map.has(r3.id)) {
24550
+ map.set(r3.id, {
24551
+ id: r3.id,
24496
24552
  source,
24497
- title: r2.title,
24498
- snippet: snippetOf(r2.snippet ?? r2.title),
24553
+ title: r3.title,
24554
+ snippet: snippetOf(r3.snippet ?? r3.title),
24499
24555
  importance: 3,
24500
- ageDays: ageExpr(r2.created),
24556
+ ageDays: ageExpr(r3.created),
24501
24557
  bm25Order: null,
24502
24558
  vecs: []
24503
24559
  });
@@ -24517,14 +24573,14 @@ function collectKnowledgeChunks(knowledgeDb, queryText, pool, nowMs, loadVec, mo
24517
24573
  WHERE knowledge_fts MATCH ?
24518
24574
  ORDER BY rank LIMIT ?`
24519
24575
  ).all(sanitizeFts5QueryOr(queryText), pool);
24520
- rows.forEach((r2, i) => {
24521
- map.set(r2.id, {
24522
- id: r2.id,
24576
+ rows.forEach((r3, i) => {
24577
+ map.set(r3.id, {
24578
+ id: r3.id,
24523
24579
  source: "knowledge_chunk",
24524
- title: r2.heading || snippetOf(r2.content, 60),
24525
- snippet: snippetOf(r2.content),
24580
+ title: r3.heading || snippetOf(r3.content, 60),
24581
+ snippet: snippetOf(r3.content),
24526
24582
  importance: 3,
24527
- ageDays: ageDaysFrom((r2.epoch ?? 0) * 1e3, nowMs),
24583
+ ageDays: ageDaysFrom((r3.epoch ?? 0) * 1e3, nowMs),
24528
24584
  bm25Order: i,
24529
24585
  vecs: []
24530
24586
  });
@@ -24556,9 +24612,9 @@ function loadEmbeddings(db, table, idCol, ids, modelId, dim, assign) {
24556
24612
  WHERE ${idCol} IN (${placeholders}) AND model_id = ? AND dim = ?
24557
24613
  LIMIT ?`
24558
24614
  ).all(...ids, modelId, dim, ids.length);
24559
- for (const r2 of rows) {
24560
- const v3 = blobToFloat32(r2.vec);
24561
- if (v3 && v3.length === dim) assign(r2.id, v3);
24615
+ for (const r3 of rows) {
24616
+ const v3 = blobToFloat32(r3.vec);
24617
+ if (v3 && v3.length === dim) assign(r3.id, v3);
24562
24618
  }
24563
24619
  } catch {
24564
24620
  }
@@ -24668,17 +24724,17 @@ function hybridSearch(memDb, knowledgeDb, opts) {
24668
24724
  ageDays: c2.ageDays
24669
24725
  };
24670
24726
  });
24671
- const top = ranked.filter((r2) => r2.score >= minScore).sort((a2, b2) => b2.score - a2.score || a2.ageDays - b2.ageDays).slice(0, limit);
24727
+ const top = ranked.filter((r3) => r3.score >= minScore).sort((a2, b2) => b2.score - a2.score || a2.ageDays - b2.ageDays).slice(0, limit);
24672
24728
  if (includeSuperseded && opts.asOf == null) {
24673
- for (const r2 of top) {
24674
- const table = r2.source === "observation" ? "observations" : r2.source === "architecture_decision" ? "architecture_decisions" : null;
24729
+ for (const r3 of top) {
24730
+ const table = r3.source === "observation" ? "observations" : r3.source === "architecture_decision" ? "architecture_decisions" : null;
24675
24731
  if (!table) continue;
24676
24732
  try {
24677
- const row = memDb.prepare(`SELECT valid_to, superseded_by, expired_at_epoch FROM ${table} WHERE id = ?`).get(r2.id);
24733
+ const row = memDb.prepare(`SELECT valid_to, superseded_by, expired_at_epoch FROM ${table} WHERE id = ?`).get(r3.id);
24678
24734
  if (row && row.expired_at_epoch != null) {
24679
24735
  const when = row.valid_to ? row.valid_to.slice(0, 10) : "an earlier date";
24680
24736
  const by = row.superseded_by != null ? ` by #${row.superseded_by}` : "";
24681
- r2.snippet = `(superseded on ${when}${by}) ${r2.snippet}`;
24737
+ r3.snippet = `(superseded on ${when}${by}) ${r3.snippet}`;
24682
24738
  }
24683
24739
  } catch {
24684
24740
  }
@@ -24808,11 +24864,11 @@ async function supersedeIfContradicted(memDb, knowledgeDb, args2) {
24808
24864
  candidatePool: 20
24809
24865
  });
24810
24866
  const scored = [];
24811
- for (const r2 of results) {
24812
- if (r2.id === args2.newId) continue;
24867
+ for (const r3 of results) {
24868
+ if (r3.id === args2.newId) continue;
24813
24869
  if (Date.now() - start > cfg.budgetMs) break;
24814
- const vec = await candidateVec(memDb, args2.source, r2.id);
24815
- if (vec) scored.push({ id: r2.id, source: args2.source, cosine: cosineSim(queryVec, vec) });
24870
+ const vec = await candidateVec(memDb, args2.source, r3.id);
24871
+ if (vec) scored.push({ id: r3.id, source: args2.source, cosine: cosineSim(queryVec, vec) });
24816
24872
  }
24817
24873
  let verdict = null;
24818
24874
  if (cfg.judgeEndpoint && Date.now() - start < cfg.budgetMs) {
@@ -24871,17 +24927,11 @@ function emptyResult() {
24871
24927
  };
24872
24928
  }
24873
24929
  function acquireLease(db, now, ttlSec) {
24874
- const raw = getMemoryMeta(db, LEASE_KEY);
24875
- if (raw) {
24876
- const expiry = Number(raw.split(":")[1]);
24877
- if (Number.isFinite(expiry) && expiry > now) return false;
24878
- }
24879
- setMemoryMeta(db, LEASE_KEY, `${process.pid}:${now + ttlSec}`);
24880
- return true;
24930
+ return tryAcquireMetaLease(db, LEASE_KEY, ttlSec, now);
24881
24931
  }
24882
24932
  function releaseLease(db) {
24883
24933
  try {
24884
- setMemoryMeta(db, LEASE_KEY, "");
24934
+ releaseMetaLease(db, LEASE_KEY);
24885
24935
  } catch {
24886
24936
  }
24887
24937
  }
@@ -24900,19 +24950,19 @@ async function stageDedupe(db, cfg, deadline, now) {
24900
24950
  return 0;
24901
24951
  }
24902
24952
  let deduped = 0;
24903
- for (const r2 of rows) {
24953
+ for (const r3 of rows) {
24904
24954
  if (Date.now() > deadline) break;
24905
- const live = db.prepare(`SELECT expired_at FROM observations WHERE id = ?`).get(r2.id);
24955
+ const live = db.prepare(`SELECT expired_at FROM observations WHERE id = ?`).get(r3.id);
24906
24956
  if (!live || live.expired_at != null) continue;
24907
24957
  const res = await supersedeIfContradicted(db, null, {
24908
- text: `${r2.title}
24909
- ${r2.detail ?? ""}`.trim(),
24958
+ text: `${r3.title}
24959
+ ${r3.detail ?? ""}`.trim(),
24910
24960
  source: "observation",
24911
- newId: r2.id,
24961
+ newId: r3.id,
24912
24962
  nowEpochSec: now
24913
24963
  });
24914
24964
  if (res.superseded != null) deduped++;
24915
- setMemoryMeta(db, DEDUPE_CURSOR, String(r2.id));
24965
+ setMemoryMeta(db, DEDUPE_CURSOR, String(r3.id));
24916
24966
  }
24917
24967
  return deduped;
24918
24968
  }
@@ -24990,11 +25040,11 @@ async function stagePromote(db, cfg, projectRoot, deadline, now, dryRun) {
24990
25040
  ).all(MAX_ROWS_PER_STAGE);
24991
25041
  if (rows.length === 0) return { promoted: 0, refusedByTier: 0, embedderUnavailable: false };
24992
25042
  const vecs = /* @__PURE__ */ new Map();
24993
- for (const r2 of rows) {
25043
+ for (const r3 of rows) {
24994
25044
  if (Date.now() > deadline) break;
24995
- const v3 = await embed(`${r2.title}
24996
- ${r2.detail ?? ""}`.trim());
24997
- if (v3) vecs.set(r2.id, v3);
25045
+ const v3 = await embed(`${r3.title}
25046
+ ${r3.detail ?? ""}`.trim());
25047
+ if (v3) vecs.set(r3.id, v3);
24998
25048
  }
24999
25049
  if (vecs.size === 0) {
25000
25050
  return { promoted: 0, refusedByTier: 0, embedderUnavailable: true };
@@ -25002,13 +25052,13 @@ ${r2.detail ?? ""}`.trim());
25002
25052
  const CLUSTER_THRESHOLD = 0.8;
25003
25053
  const used = /* @__PURE__ */ new Set();
25004
25054
  const clusters = [];
25005
- for (const r2 of rows) {
25006
- if (used.has(r2.id) || !vecs.has(r2.id)) continue;
25007
- const cluster = [r2];
25008
- used.add(r2.id);
25055
+ for (const r3 of rows) {
25056
+ if (used.has(r3.id) || !vecs.has(r3.id)) continue;
25057
+ const cluster = [r3];
25058
+ used.add(r3.id);
25009
25059
  for (const other of rows) {
25010
25060
  if (used.has(other.id) || !vecs.has(other.id)) continue;
25011
- if (cosineSim(vecs.get(r2.id), vecs.get(other.id)) >= CLUSTER_THRESHOLD) {
25061
+ if (cosineSim(vecs.get(r3.id), vecs.get(other.id)) >= CLUSTER_THRESHOLD) {
25012
25062
  cluster.push(other);
25013
25063
  used.add(other.id);
25014
25064
  }
@@ -25107,14 +25157,14 @@ function stageReweight(db, cfg, now) {
25107
25157
  ON CONFLICT(source, record_id) DO UPDATE SET last_reweight_epoch = excluded.last_reweight_epoch`
25108
25158
  );
25109
25159
  const tx = db.transaction(() => {
25110
- for (const r2 of promote) {
25111
- bump.run(r2.id);
25112
- mark.run(r2.id, now);
25160
+ for (const r3 of promote) {
25161
+ bump.run(r3.id);
25162
+ mark.run(r3.id, now);
25113
25163
  changed++;
25114
25164
  }
25115
- for (const r2 of demote) {
25116
- drop.run(r2.id);
25117
- mark.run(r2.id, now);
25165
+ for (const r3 of demote) {
25166
+ drop.run(r3.id);
25167
+ mark.run(r3.id, now);
25118
25168
  changed++;
25119
25169
  }
25120
25170
  });
@@ -28060,8 +28110,8 @@ async function runAdaptersList(_args) {
28060
28110
  `);
28061
28111
  }
28062
28112
  } else {
28063
- for (const r2 of manifestResult.reasons) {
28064
- process.stderr.write(`[manifest] ${r2}
28113
+ for (const r3 of manifestResult.reasons) {
28114
+ process.stderr.write(`[manifest] ${r3}
28065
28115
  `);
28066
28116
  }
28067
28117
  }
@@ -28980,9 +29030,9 @@ var init_security_utils = __esm({
28980
29030
 
28981
29031
  // src/rules.ts
28982
29032
  function getPatternRules() {
28983
- return getConfig().rules.map((r2) => ({
28984
- match: r2.pattern,
28985
- rules: r2.rules
29033
+ return getConfig().rules.map((r3) => ({
29034
+ match: r3.pattern,
29035
+ rules: r3.rules
28986
29036
  }));
28987
29037
  }
28988
29038
  function matchRules(filePath) {
@@ -31244,8 +31294,8 @@ function handleSearch(args2, db) {
31244
31294
  const lines = [`## Search Results for "${query}" (${results.length} matches)`, ""];
31245
31295
  lines.push("| ID | Type | Title | Date | Importance |");
31246
31296
  lines.push("|----|------|-------|------|------------|");
31247
- for (const r2 of results) {
31248
- lines.push(`| ${r2.id} | ${r2.type} | ${r2.title.slice(0, 80)} | ${r2.created_at.split("T")[0]} | ${r2.importance} |`);
31297
+ for (const r3 of results) {
31298
+ lines.push(`| ${r3.id} | ${r3.type} | ${r3.title.slice(0, 80)} | ${r3.created_at.split("T")[0]} | ${r3.importance} |`);
31249
31299
  }
31250
31300
  lines.push("");
31251
31301
  lines.push(`Use ${p3("memory_detail")} with IDs for full details.`);
@@ -31678,9 +31728,9 @@ function handleDocsAudit(args2) {
31678
31728
  }
31679
31729
  }
31680
31730
  }
31681
- const staleCount = results.filter((r2) => r2.status === "STALE").length;
31682
- const newCount = results.filter((r2) => r2.status === "NEW").length;
31683
- const okCount = results.filter((r2) => r2.status === "OK").length;
31731
+ const staleCount = results.filter((r3) => r3.status === "STALE").length;
31732
+ const newCount = results.filter((r3) => r3.status === "NEW").length;
31733
+ const okCount = results.filter((r3) => r3.status === "OK").length;
31684
31734
  const report = {
31685
31735
  affectedPages: results,
31686
31736
  summary: `${results.length} pages checked: ${staleCount} STALE, ${newCount} NEW, ${okCount} OK. ${staleCount + newCount > 0 ? `${staleCount + newCount} pages need updates.` : "All docs are current."}`
@@ -31932,8 +31982,8 @@ function handlePromptAnalysis(args2, db) {
31932
31982
  const lines = [`## Prompt Search: "${query}" (${results.length} results)`, ""];
31933
31983
  lines.push("| Session | Turn | Prompt (truncated) | Tools | Response Tokens | Date |");
31934
31984
  lines.push("|---------|------|--------------------|-------|-----------------|------|");
31935
- for (const r2 of results) {
31936
- lines.push(`| ${r2.session_id.slice(0, 8)}... | ${r2.turn_number} | ${r2.user_prompt.slice(0, 60).replace(/\|/g, "\\|")} | ${r2.tool_call_count} | ${r2.response_tokens ?? "-"} | ${r2.created_at.split("T")[0]} |`);
31985
+ for (const r3 of results) {
31986
+ lines.push(`| ${r3.session_id.slice(0, 8)}... | ${r3.turn_number} | ${r3.user_prompt.slice(0, 60).replace(/\|/g, "\\|")} | ${r3.tool_call_count} | ${r3.response_tokens ?? "-"} | ${r3.created_at.split("T")[0]} |`);
31937
31987
  }
31938
31988
  return text4(lines.join("\n"));
31939
31989
  }
@@ -33373,12 +33423,49 @@ var init_analytics = __esm({
33373
33423
  }
33374
33424
  });
33375
33425
 
33426
+ // src/model-pricing.ts
33427
+ var PRICE_TABLE_SOURCE, PRICE_TABLE_AS_OF, r2, US, BUILT_IN_PRICES;
33428
+ var init_model_pricing = __esm({
33429
+ "src/model-pricing.ts"() {
33430
+ "use strict";
33431
+ PRICE_TABLE_SOURCE = "https://platform.claude.com/docs/en/about-claude/pricing";
33432
+ PRICE_TABLE_AS_OF = "2026-11-09";
33433
+ r2 = (input, cacheWrite5m, cacheWrite1h, cacheRead, output) => ({ input, cacheWrite5m, cacheWrite1h, cacheRead, output });
33434
+ US = 1.1;
33435
+ BUILT_IN_PRICES = {
33436
+ "claude-fable-5-1": { ...r2(10, 12.5, 20, 0.25, 50), usGeoMultiplier: US },
33437
+ "claude-mythos-5-1": { ...r2(10, 12.5, 20, 0.25, 50), usGeoMultiplier: US },
33438
+ "claude-fable-5": { ...r2(10, 12.5, 20, 1, 50), usGeoMultiplier: US },
33439
+ "claude-mythos-5": { ...r2(10, 12.5, 20, 1, 50), usGeoMultiplier: US },
33440
+ "claude-opus-5-5": { ...r2(4, 5, 8, 0.2, 20), fast: { input: 8, output: 40 }, usGeoMultiplier: US },
33441
+ "claude-opus-5": { ...r2(5, 6.25, 10, 0.5, 25), fast: { input: 10, output: 50 }, usGeoMultiplier: US },
33442
+ "claude-opus-4-8": { ...r2(5, 6.25, 10, 0.5, 25), fast: { input: 10, output: 50 }, usGeoMultiplier: US },
33443
+ "claude-opus-4-7": { ...r2(5, 6.25, 10, 0.5, 25), usGeoMultiplier: US },
33444
+ "claude-opus-4-6": { ...r2(5, 6.25, 10, 0.5, 25), usGeoMultiplier: US },
33445
+ "claude-opus-4-5": r2(5, 6.25, 10, 0.5, 25),
33446
+ "claude-opus-4-1": r2(15, 18.75, 30, 1.5, 75),
33447
+ "claude-opus-4": r2(15, 18.75, 30, 1.5, 75),
33448
+ "claude-sonnet-5-5": { ...r2(2, 2.5, 4, 0.1, 10), usGeoMultiplier: US },
33449
+ "claude-sonnet-5": { ...r2(2, 2.5, 4, 0.2, 10), usGeoMultiplier: US },
33450
+ "claude-sonnet-4-6": { ...r2(3, 3.75, 6, 0.3, 15), usGeoMultiplier: US },
33451
+ "claude-sonnet-4-5": r2(3, 3.75, 6, 0.3, 15),
33452
+ "claude-sonnet-4": r2(3, 3.75, 6, 0.3, 15),
33453
+ "claude-haiku-5-5": {
33454
+ ...r2(0.1, 0.125, 0.2, 0.01, 0.5),
33455
+ longPrompt: { overTokens: 1e5, rates: r2(0.5, 0.625, 1, 0.05, 2.5) },
33456
+ usGeoMultiplier: US
33457
+ },
33458
+ "claude-haiku-4-5": r2(1, 1.25, 2, 0.1, 5)
33459
+ };
33460
+ }
33461
+ });
33462
+
33376
33463
  // src/cost-tracker.ts
33377
33464
  function p8(baseName) {
33378
33465
  return `${getConfig().toolPrefix}_${baseName}`;
33379
33466
  }
33380
- function getModelPricing() {
33381
- return getConfig().analytics?.cost?.models ?? DEFAULT_MODEL_PRICING;
33467
+ function getCurrency() {
33468
+ return getConfig().analytics?.cost?.currency ?? "USD";
33382
33469
  }
33383
33470
  function getCostToolDefinitions() {
33384
33471
  return [
@@ -33452,25 +33539,31 @@ function handleCostSession(args2, db) {
33452
33539
  if (!cost) {
33453
33540
  return text7(`No cost data found for session ${sessionId}. Cost data is recorded automatically when sessions end via the session-end hook. If the session is still active, data will appear after it completes. Try: ${p8("cost_trend")} { days: 30 } to see aggregate cost data instead.`);
33454
33541
  }
33455
- const modelPricing = getModelPricing();
33456
- const mp = modelPricing[cost.model] ?? modelPricing["claude-sonnet-4-5"] ?? { input_per_million: 3, output_per_million: 15 };
33457
- const inputCost = cost.input_tokens / 1e6 * mp.input_per_million;
33458
- const outputCost = cost.output_tokens / 1e6 * mp.output_per_million;
33459
- const cacheReadCost = cost.cache_read_tokens / 1e6 * (mp.cache_read_per_million ?? 0);
33460
- const cacheWriteCost = cost.cache_write_tokens / 1e6 * (mp.cache_write_per_million ?? 0);
33542
+ const status = cost.cost_status ?? "unknown";
33543
+ const dollars = cost.estimated_cost_usd;
33544
+ const headline = status === "unknown" ? "unknown" : `$${dollars.toFixed(4)}${status === "partial" ? " (partial, see below)" : ""}`;
33461
33545
  const lines = [
33462
- `## Session Cost: $${cost.estimated_cost_usd.toFixed(4)}`,
33463
- `Model: ${cost.model}`,
33546
+ `## Session Cost: ${headline}`,
33547
+ `Model: ${cost.model ?? "unknown"}`,
33464
33548
  "",
33465
33549
  "### Token Usage",
33466
- `| Type | Tokens | Est. Cost |`,
33467
- `|------|--------|-----------|`,
33468
- `| Input | ${cost.input_tokens.toLocaleString()} | $${inputCost.toFixed(4)} |`,
33469
- `| Output | ${cost.output_tokens.toLocaleString()} | $${outputCost.toFixed(4)} |`,
33470
- `| Cache Read | ${cost.cache_read_tokens.toLocaleString()} | $${cacheReadCost.toFixed(4)} |`,
33471
- `| Cache Write | ${cost.cache_write_tokens.toLocaleString()} | $${cacheWriteCost.toFixed(4)} |`,
33472
- `| **Total** | ${cost.total_tokens.toLocaleString()} | **$${cost.estimated_cost_usd.toFixed(4)}** |`
33550
+ `| Type | Tokens |`,
33551
+ `|------|--------|`,
33552
+ `| Input | ${cost.input_tokens.toLocaleString()} |`,
33553
+ `| Output | ${cost.output_tokens.toLocaleString()} |`,
33554
+ `| Cache Read | ${cost.cache_read_tokens.toLocaleString()} |`,
33555
+ `| Cache Write | ${cost.cache_write_tokens.toLocaleString()} |`,
33556
+ `| **Total** | ${cost.total_tokens.toLocaleString()} |`,
33557
+ "",
33558
+ `Responses priced: ${cost.priced_responses ?? 0}; without a price: ${cost.unpriced_responses ?? 0}.`
33473
33559
  ];
33560
+ if (status === "unknown" && !cost.unpriced_responses) {
33561
+ lines.push("No transcript was available to price this session.");
33562
+ }
33563
+ if (cost.unpriced_models) {
33564
+ lines.push(`No price known for: ${cost.unpriced_models}. Add it under analytics.cost.models in massu.config.yaml.`);
33565
+ }
33566
+ lines.push(`Prices: ${PRICE_TABLE_SOURCE} as of ${PRICE_TABLE_AS_OF} (${getCurrency()}).`);
33474
33567
  return text7(lines.join("\n"));
33475
33568
  }
33476
33569
  function handleCostTrend(args2, db) {
@@ -33481,17 +33574,21 @@ function handleCostTrend(args2, db) {
33481
33574
  }
33482
33575
  const sql = groupBy === "week" ? `SELECT strftime('%Y-W%W', created_at) as period,
33483
33576
  COUNT(*) as sessions,
33484
- SUM(estimated_cost_usd) as total_cost,
33577
+ SUM(CASE WHEN cost_status = 'unknown' THEN 0 ELSE estimated_cost_usd END) as total_cost,
33485
33578
  SUM(total_tokens) as total_tokens,
33486
- AVG(estimated_cost_usd) as avg_cost
33579
+ SUM(CASE WHEN cost_status = 'priced' THEN 1 ELSE 0 END) as priced_sessions,
33580
+ SUM(CASE WHEN cost_status = 'partial' THEN 1 ELSE 0 END) as partial_sessions,
33581
+ SUM(CASE WHEN cost_status = 'unknown' THEN 1 ELSE 0 END) as unknown_sessions
33487
33582
  FROM session_costs
33488
33583
  WHERE created_at >= datetime('now', ?)
33489
33584
  GROUP BY period
33490
33585
  ORDER BY period ASC` : `SELECT strftime('%Y-%m-%d', created_at) as period,
33491
33586
  COUNT(*) as sessions,
33492
- SUM(estimated_cost_usd) as total_cost,
33587
+ SUM(CASE WHEN cost_status = 'unknown' THEN 0 ELSE estimated_cost_usd END) as total_cost,
33493
33588
  SUM(total_tokens) as total_tokens,
33494
- AVG(estimated_cost_usd) as avg_cost
33589
+ SUM(CASE WHEN cost_status = 'priced' THEN 1 ELSE 0 END) as priced_sessions,
33590
+ SUM(CASE WHEN cost_status = 'partial' THEN 1 ELSE 0 END) as partial_sessions,
33591
+ SUM(CASE WHEN cost_status = 'unknown' THEN 1 ELSE 0 END) as unknown_sessions
33495
33592
  FROM session_costs
33496
33593
  WHERE created_at >= datetime('now', ?)
33497
33594
  GROUP BY period
@@ -33500,22 +33597,28 @@ function handleCostTrend(args2, db) {
33500
33597
  if (rows.length === 0) {
33501
33598
  return text7(`No cost data found in the last ${days} days. Cost tracking records token usage automatically at session end via hooks. Ensure session-end hooks are configured in your settings. Try: ${p8("cost_session")} { session_id: "..." } to check a specific session.`);
33502
33599
  }
33503
- const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
33504
- const totalSessions = rows.reduce((sum, r2) => sum + r2.sessions, 0);
33600
+ const sum = (k3) => rows.reduce((acc, r3) => acc + (r3[k3] ?? 0), 0);
33601
+ const totalCost = sum("total_cost");
33602
+ const totalSessions = sum("sessions");
33603
+ const known = sum("priced_sessions") + sum("partial_sessions");
33604
+ const unknown = sum("unknown_sessions");
33605
+ const partial = sum("partial_sessions");
33505
33606
  const lines = [
33506
33607
  `## Cost Trend (${days} days)`,
33507
- `Total: $${totalCost.toFixed(2)} across ${totalSessions} sessions`,
33508
- `Average per session: $${(totalCost / totalSessions).toFixed(4)}`,
33608
+ `Total: $${totalCost.toFixed(2)} across ${known} priced session(s)` + (unknown ? `; ${unknown} session(s) of ${totalSessions} have unknown cost and are NOT in this total` : ""),
33609
+ known ? `Average per priced session: $${(totalCost / known).toFixed(4)}` : "No session has a known cost in this window."
33610
+ ];
33611
+ if (partial) lines.push(`${partial} session(s) are partial: a model they used has no price, so they are under-counted.`);
33612
+ lines.push(
33509
33613
  "",
33510
33614
  `### By ${groupBy === "week" ? "Week" : "Day"}`,
33511
- `| Period | Sessions | Cost | Avg/Session |`,
33512
- `|--------|----------|------|-------------|`
33513
- ];
33615
+ `| Period | Sessions | Cost | Unknown |`,
33616
+ `|--------|----------|------|---------|`
33617
+ );
33514
33618
  for (const row of rows) {
33515
- lines.push(
33516
- `| ${row.period} | ${row.sessions} | $${row.total_cost.toFixed(2)} | $${row.avg_cost.toFixed(4)} |`
33517
- );
33619
+ lines.push(`| ${row.period} | ${row.sessions} | $${row.total_cost.toFixed(2)} | ${row.unknown_sessions} |`);
33518
33620
  }
33621
+ lines.push("", `Prices: ${PRICE_TABLE_SOURCE} as of ${PRICE_TABLE_AS_OF} (${getCurrency()}).`);
33519
33622
  return text7(lines.join("\n"));
33520
33623
  }
33521
33624
  function handleCostFeature(args2, db) {
@@ -33528,12 +33631,13 @@ function handleCostFeature(args2, db) {
33528
33631
  ORDER BY total_cost DESC
33529
33632
  `).all(`-${days} days`);
33530
33633
  if (rows.length === 0) {
33531
- return text7(`No feature cost data found in the last ${days} days. Feature costs are attributed automatically when sessions work on registered features. Ensure features are registered, then costs are tracked per-session. Try: ${p8("cost_trend")} { days: 30 } for session-level cost trends instead.`);
33634
+ return text7(`No feature cost data found in the last ${days} days. Each session's priced cost is allocated across registered features (massu_sentinel_register) by the files it touched; sessions with unknown cost are skipped. Try: ${p8("cost_trend")} { days: 30 } for session-level cost trends instead.`);
33532
33635
  }
33533
- const totalCost = rows.reduce((sum, r2) => sum + r2.total_cost, 0);
33636
+ const totalCost = rows.reduce((sum, r3) => sum + r3.total_cost, 0);
33534
33637
  const lines = [
33535
33638
  `## Feature Cost Attribution (${days} days)`,
33536
- `Total: $${totalCost.toFixed(2)} across ${rows.length} features`,
33639
+ `Total: $${totalCost.toFixed(2)} across ${rows.length} feature row(s)`,
33640
+ "Each session's priced cost is ALLOCATED across the registered features whose files it touched, in proportion to those touches. It is an allocation by activity, not a measured per-feature cost. Sessions with unknown cost are not included.",
33537
33641
  "",
33538
33642
  "| Feature | Cost | Tokens | Sessions | % of Total |",
33539
33643
  "|---------|------|--------|----------|------------|"
@@ -33549,18 +33653,12 @@ function handleCostFeature(args2, db) {
33549
33653
  function text7(content) {
33550
33654
  return { content: [{ type: "text", text: content }] };
33551
33655
  }
33552
- var DEFAULT_MODEL_PRICING, COST_BASE_NAMES;
33656
+ var COST_BASE_NAMES;
33553
33657
  var init_cost_tracker = __esm({
33554
33658
  "src/cost-tracker.ts"() {
33555
33659
  "use strict";
33556
33660
  init_config();
33557
- DEFAULT_MODEL_PRICING = {
33558
- "claude-opus-4-6": { input_per_million: 15, output_per_million: 75, cache_read_per_million: 1.5, cache_write_per_million: 18.75 },
33559
- "claude-sonnet-4-6": { input_per_million: 3, output_per_million: 15, cache_read_per_million: 0.3, cache_write_per_million: 3.75 },
33560
- "claude-sonnet-4-5": { input_per_million: 3, output_per_million: 15, cache_read_per_million: 0.3, cache_write_per_million: 3.75 },
33561
- "claude-haiku-4-5-20251001": { input_per_million: 0.8, output_per_million: 4, cache_read_per_million: 0.08, cache_write_per_million: 1 },
33562
- "default": { input_per_million: 3, output_per_million: 15, cache_read_per_million: 0.3, cache_write_per_million: 3.75 }
33563
- };
33661
+ init_model_pricing();
33564
33662
  COST_BASE_NAMES = /* @__PURE__ */ new Set(["cost_session", "cost_trend", "cost_feature"]);
33565
33663
  }
33566
33664
  });
@@ -33973,8 +34071,8 @@ function handleValidationReport(args2, db) {
33973
34071
  if (results.length === 0) {
33974
34072
  return text9(`No validation results found in the last ${days} days. Run ${p10("validation_check")} { file: "src/path/to/file.ts" } on specific files to generate validation data, or try a longer time range with { days: 90 }.`);
33975
34073
  }
33976
- const passedCount = results.filter((r2) => r2.passed === 1).length;
33977
- const failedCount = results.filter((r2) => r2.passed === 0).length;
34074
+ const passedCount = results.filter((r3) => r3.passed === 1).length;
34075
+ const failedCount = results.filter((r3) => r3.passed === 0).length;
33978
34076
  const lines = [
33979
34077
  `## Validation Report (${days} days)`,
33980
34078
  `Files validated: ${results.length}`,
@@ -33985,11 +34083,11 @@ function handleValidationReport(args2, db) {
33985
34083
  "| File | Rules Violated | Date |",
33986
34084
  "|------|----------------|------|"
33987
34085
  ];
33988
- const failedResults = results.filter((r2) => r2.passed === 0);
33989
- for (const r2 of failedResults.slice(0, 30)) {
33990
- const filename = r2.file_path.split("/").pop();
33991
- const rules = r2.rules_violated ?? "-";
33992
- lines.push(`| ${filename} | ${rules.slice(0, 60)} | ${r2.created_at.slice(0, 10)} |`);
34086
+ const failedResults = results.filter((r3) => r3.passed === 0);
34087
+ for (const r3 of failedResults.slice(0, 30)) {
34088
+ const filename = r3.file_path.split("/").pop();
34089
+ const rules = r3.rules_violated ?? "-";
34090
+ lines.push(`| ${filename} | ${rules.slice(0, 60)} | ${r3.created_at.slice(0, 10)} |`);
33993
34091
  }
33994
34092
  if (failedResults.length > 30) {
33995
34093
  lines.push(`... and ${failedResults.length - 30} more`);
@@ -34918,9 +35016,9 @@ function handleTeamSearch(args2, db) {
34918
35016
  "| Developer | Type | Summary | Module | Date |",
34919
35017
  "|-----------|------|---------|--------|------|"
34920
35018
  ];
34921
- for (const r2 of results) {
35019
+ for (const r3 of results) {
34922
35020
  lines.push(
34923
- `| ${r2.developer_id} | ${r2.observation_type} | ${r2.summary.slice(0, 60)} | ${r2.module ?? "-"} | ${r2.created_at.split("T")[0]} |`
35021
+ `| ${r3.developer_id} | ${r3.observation_type} | ${r3.summary.slice(0, 60)} | ${r3.module ?? "-"} | ${r3.created_at.split("T")[0]} |`
34924
35022
  );
34925
35023
  }
34926
35024
  return text13(lines.join("\n"));
@@ -36013,10 +36111,10 @@ function handleSearch3(db, args2) {
36013
36111
  } else {
36014
36112
  lines.push(`Found ${results.length} result(s):`);
36015
36113
  lines.push("");
36016
- for (const r2 of results) {
36017
- const preview = r2.content.length > 500 ? r2.content.substring(0, 500) + "..." : r2.content;
36018
- lines.push(`### ${r2.heading || r2.title} [${r2.chunk_type}]`);
36019
- lines.push(`**File**: ${claudeDir}/${r2.file_path} | **Category**: ${r2.category}`);
36114
+ for (const r3 of results) {
36115
+ const preview = r3.content.length > 500 ? r3.content.substring(0, 500) + "..." : r3.content;
36116
+ lines.push(`### ${r3.heading || r3.title} [${r3.chunk_type}]`);
36117
+ lines.push(`**File**: ${claudeDir}/${r3.file_path} | **Category**: ${r3.category}`);
36020
36118
  lines.push("");
36021
36119
  lines.push(preview);
36022
36120
  lines.push("");
@@ -36250,11 +36348,11 @@ function handlePattern(db, args2) {
36250
36348
  LIMIT 10
36251
36349
  `).all(sanitizeFts5Query(topic), ...docIds);
36252
36350
  if (topicResults.length > 0) {
36253
- for (const r2 of topicResults) {
36254
- lines.push(`### ${r2.heading || "(section)"} [${r2.chunk_type}]`);
36255
- lines.push(`*Source: ${claudeDir}/${r2.file_path}*`);
36351
+ for (const r3 of topicResults) {
36352
+ lines.push(`### ${r3.heading || "(section)"} [${r3.chunk_type}]`);
36353
+ lines.push(`*Source: ${claudeDir}/${r3.file_path}*`);
36256
36354
  lines.push("");
36257
- lines.push(r2.content.length > 800 ? r2.content.substring(0, 800) + "..." : r2.content);
36355
+ lines.push(r3.content.length > 800 ? r3.content.substring(0, 800) + "..." : r3.content);
36258
36356
  lines.push("");
36259
36357
  lines.push("---");
36260
36358
  lines.push("");
@@ -36298,8 +36396,8 @@ function handlePattern(db, args2) {
36298
36396
  if (ftsResults.length > 0) {
36299
36397
  lines.push("### Broad Search Results");
36300
36398
  lines.push("");
36301
- for (const r2 of ftsResults) {
36302
- lines.push(`- **${r2.heading || "(section)"}** (${claudeDir}/${r2.file_path}): ${r2.content.substring(0, 200)}...`);
36399
+ for (const r3 of ftsResults) {
36400
+ lines.push(`- **${r3.heading || "(section)"}** (${claudeDir}/${r3.file_path}): ${r3.content.substring(0, 200)}...`);
36303
36401
  }
36304
36402
  }
36305
36403
  } catch {
@@ -36447,9 +36545,9 @@ function handleCommand(db, args2) {
36447
36545
  `).all(`%${keyword}%`, `%${keyword}%`);
36448
36546
  lines.push(`## Commands matching "${keyword}" (${results.length} found)`);
36449
36547
  lines.push("");
36450
- for (const r2 of results) {
36451
- const preview = r2.content.substring(0, 200).replace(/\n/g, " ");
36452
- lines.push(`- **${r2.heading}** (${claudeDir}/${r2.file_path}): ${preview}...`);
36548
+ for (const r3 of results) {
36549
+ const preview = r3.content.substring(0, 200).replace(/\n/g, " ");
36550
+ lines.push(`- **${r3.heading}** (${claudeDir}/${r3.file_path}): ${preview}...`);
36453
36551
  }
36454
36552
  } else {
36455
36553
  const commands = db.prepare(`
@@ -36534,8 +36632,8 @@ function handlePlan(db, args2) {
36534
36632
  `).all(`%${basename12(file)}%`);
36535
36633
  lines.push(`## Plans referencing \`${file}\` (${results.length} found)`);
36536
36634
  lines.push("");
36537
- for (const r2 of results) {
36538
- lines.push(`- **${r2.title}** (${r2.file_path})`);
36635
+ for (const r3 of results) {
36636
+ lines.push(`- **${r3.title}** (${r3.file_path})`);
36539
36637
  }
36540
36638
  } else if (keyword) {
36541
36639
  try {
@@ -36550,8 +36648,8 @@ function handlePlan(db, args2) {
36550
36648
  `).all(sanitizeFts5Query(keyword));
36551
36649
  lines.push(`## Plans matching "${keyword}" (${results.length} found)`);
36552
36650
  lines.push("");
36553
- for (const r2 of results) {
36554
- lines.push(`- **${r2.title}** (${r2.file_path})`);
36651
+ for (const r3 of results) {
36652
+ lines.push(`- **${r3.title}** (${r3.file_path})`);
36555
36653
  }
36556
36654
  } catch {
36557
36655
  lines.push(`FTS search error for "${keyword}". Try simpler terms.`);
@@ -36744,7 +36842,7 @@ function handleEffectiveness2(db, args2) {
36744
36842
  }
36745
36843
  if (ruleId || mode === "detail") {
36746
36844
  const targetRule = ruleId || "";
36747
- const rule = allRules.find((r2) => r2.rule_id === targetRule);
36845
+ const rule = allRules.find((r3) => r3.rule_id === targetRule);
36748
36846
  if (!rule) {
36749
36847
  return text15(`Rule ${targetRule} not found.`);
36750
36848
  }
@@ -36767,10 +36865,10 @@ function handleEffectiveness2(db, args2) {
36767
36865
  lines.push(`| Linked Corrections | ${correctionEdges.cnt} |`);
36768
36866
  lines.push(`| Effectiveness Score | ${violations === 0 ? "HIGH (never violated)" : violations < 3 ? "MEDIUM" : "LOW (frequently violated)"} |`);
36769
36867
  } else {
36770
- const rulesWithCounts = allRules.map((r2) => ({
36771
- ...r2,
36772
- violations: violationCounts.get(r2.rule_id) || 0,
36773
- last_at: lastViolated.get(r2.rule_id) || "Never"
36868
+ const rulesWithCounts = allRules.map((r3) => ({
36869
+ ...r3,
36870
+ violations: violationCounts.get(r3.rule_id) || 0,
36871
+ last_at: lastViolated.get(r3.rule_id) || "Never"
36774
36872
  }));
36775
36873
  if (mode === "most_violated") {
36776
36874
  rulesWithCounts.sort((a2, b2) => b2.violations - a2.violations);
@@ -36786,9 +36884,9 @@ function handleEffectiveness2(db, args2) {
36786
36884
  lines.push("");
36787
36885
  lines.push(`| Rule | Text | Violations | Last Violated |`);
36788
36886
  lines.push(`|------|------|------------|---------------|`);
36789
- for (const r2 of rulesWithCounts.slice(0, topN)) {
36790
- const ruleText = r2.rule_text.length > 40 ? r2.rule_text.substring(0, 40) + "..." : r2.rule_text;
36791
- lines.push(`| ${r2.rule_id} | ${ruleText} | ${r2.violations} | ${r2.last_at} |`);
36887
+ for (const r3 of rulesWithCounts.slice(0, topN)) {
36888
+ const ruleText = r3.rule_text.length > 40 ? r3.rule_text.substring(0, 40) + "..." : r3.rule_text;
36889
+ lines.push(`| ${r3.rule_id} | ${ruleText} | ${r3.violations} | ${r3.last_at} |`);
36792
36890
  }
36793
36891
  }
36794
36892
  return text15(lines.join("\n"));
@@ -37085,21 +37183,21 @@ function handlePyRoutes(args2, dataDb) {
37085
37183
  const routes = dataDb.prepare(query).all(...params);
37086
37184
  if (args2.uncoupled) {
37087
37185
  const callerCountStmt = dataDb.prepare(`SELECT COUNT(*) as cnt FROM ${t("py_route_callers")} WHERE route_id = ?`);
37088
- const uncoupledRoutes = routes.filter((r2) => {
37089
- const callers = callerCountStmt.get(r2.id).cnt;
37186
+ const uncoupledRoutes = routes.filter((r3) => {
37187
+ const callers = callerCountStmt.get(r3.id).cnt;
37090
37188
  return callers === 0;
37091
37189
  });
37092
37190
  lines.push(`## Uncoupled Routes (${uncoupledRoutes.length} with no frontend callers)`);
37093
- for (const r2 of uncoupledRoutes) {
37094
- lines.push(`- ${r2.method} ${r2.path} \u2192 ${r2.function_name} (${r2.file}:${r2.line})`);
37191
+ for (const r3 of uncoupledRoutes) {
37192
+ lines.push(`- ${r3.method} ${r3.path} \u2192 ${r3.function_name} (${r3.file}:${r3.line})`);
37095
37193
  }
37096
37194
  } else {
37097
37195
  lines.push(`## Python Routes (${routes.length} found)`);
37098
- for (const r2 of routes) {
37099
- const auth = r2.is_authenticated ? "" : " [UNAUTH]";
37100
- const deps = safeParseArray(r2.dependencies);
37101
- lines.push(`- ${r2.method} ${r2.path} \u2192 ${r2.function_name} (${r2.file}:${r2.line})${auth}`);
37102
- if (r2.response_model) lines.push(` Response: ${r2.response_model}`);
37196
+ for (const r3 of routes) {
37197
+ const auth = r3.is_authenticated ? "" : " [UNAUTH]";
37198
+ const deps = safeParseArray(r3.dependencies);
37199
+ lines.push(`- ${r3.method} ${r3.path} \u2192 ${r3.function_name} (${r3.file}:${r3.line})${auth}`);
37200
+ if (r3.response_model) lines.push(` Response: ${r3.response_model}`);
37103
37201
  if (deps.length > 0) lines.push(` Depends: ${deps.join(", ")}`);
37104
37202
  }
37105
37203
  }
@@ -37301,8 +37399,8 @@ function handlePyImpact(args2, dataDb) {
37301
37399
  if (routes.length > 0) {
37302
37400
  lines.push(`
37303
37401
  ### Routes Defined (${routes.length})`);
37304
- for (const r2 of routes) {
37305
- lines.push(`- ${r2.method} ${r2.path} \u2192 ${r2.function_name}`);
37402
+ for (const r3 of routes) {
37403
+ lines.push(`- ${r3.method} ${r3.path} \u2192 ${r3.function_name}`);
37306
37404
  }
37307
37405
  }
37308
37406
  const models = dataDb.prepare(
@@ -37320,7 +37418,7 @@ function handlePyImpact(args2, dataDb) {
37320
37418
  const placeholders = routeIds.map(() => "?").join(",");
37321
37419
  const callers = dataDb.prepare(
37322
37420
  `SELECT DISTINCT frontend_file FROM ${t("py_route_callers")} WHERE route_id IN (${placeholders}) LIMIT 100000`
37323
- ).all(...routeIds.map((r2) => r2.id));
37421
+ ).all(...routeIds.map((r3) => r3.id));
37324
37422
  if (callers.length > 0) {
37325
37423
  lines.push(`
37326
37424
  ### Frontend Callers (${callers.length} files)`);
@@ -37363,8 +37461,8 @@ function handlePyContext(args2, dataDb) {
37363
37461
  const routes = dataDb.prepare(`SELECT method, path, function_name, line FROM ${t("py_routes")} WHERE file = ? LIMIT 10000`).all(file);
37364
37462
  if (routes.length > 0) {
37365
37463
  lines.push("\n### Routes");
37366
- for (const r2 of routes) {
37367
- lines.push(`- ${r2.method} ${r2.path} \u2192 ${r2.function_name} (L${r2.line})`);
37464
+ for (const r3 of routes) {
37465
+ lines.push(`- ${r3.method} ${r3.path} \u2192 ${r3.function_name} (L${r3.line})`);
37368
37466
  }
37369
37467
  }
37370
37468
  const models = dataDb.prepare(`SELECT class_name, table_name, line FROM ${t("py_models")} WHERE file = ? LIMIT 10000`).all(file);
@@ -37375,8 +37473,8 @@ function handlePyContext(args2, dataDb) {
37375
37473
  }
37376
37474
  }
37377
37475
  const rules = config.rules || [];
37378
- const matchingRules = rules.filter((r2) => {
37379
- const pattern = r2.pattern;
37476
+ const matchingRules = rules.filter((r3) => {
37477
+ const pattern = r3.pattern;
37380
37478
  if (pattern === "**") return true;
37381
37479
  if (pattern.includes("**/*.py")) return file.endsWith(".py");
37382
37480
  return file.includes(pattern.replace(/\*\*/g, ""));
@@ -37384,8 +37482,8 @@ function handlePyContext(args2, dataDb) {
37384
37482
  if (matchingRules.length > 0) {
37385
37483
  lines.push("\n### Applicable Rules");
37386
37484
  for (const rule of matchingRules) {
37387
- for (const r2 of rule.rules) {
37388
- lines.push(`- ${r2}`);
37485
+ for (const r3 of rule.rules) {
37486
+ lines.push(`- ${r3}`);
37389
37487
  }
37390
37488
  }
37391
37489
  }
@@ -37791,8 +37889,8 @@ function handleContext(file, dataDb, codegraphDb) {
37791
37889
  lines.push("## Applicable Rules");
37792
37890
  for (const rule of rules) {
37793
37891
  const severity = rule.severity ? `[${rule.severity}]` : "";
37794
- for (const r2 of rule.rules) {
37795
- lines.push(`- ${severity} ${r2}`);
37892
+ for (const r3 of rule.rules) {
37893
+ lines.push(`- ${severity} ${r3}`);
37796
37894
  }
37797
37895
  if (rule.patternFile) {
37798
37896
  lines.push(` See: .claude/${rule.patternFile}`);
@@ -37880,7 +37978,7 @@ function handleContext(file, dataDb, codegraphDb) {
37880
37978
  WHERE kf.content MATCH ? AND kc.chunk_type = 'rule'
37881
37979
  LIMIT 8
37882
37980
  `).all(ftsQuery);
37883
- ftsRuleIds = ftsResults.map((r2) => r2.rule_id).filter((id) => id.startsWith("CR-"));
37981
+ ftsRuleIds = ftsResults.map((r3) => r3.rule_id).filter((id) => id.startsWith("CR-"));
37884
37982
  } catch {
37885
37983
  }
37886
37984
  }
@@ -38234,7 +38332,7 @@ function handleDomains(args2, dataDb, codegraphDb) {
38234
38332
  }
38235
38333
  lines.push("");
38236
38334
  lines.push(`### Routers (${files.routers.length})`);
38237
- for (const r2 of files.routers) lines.push(`- ${r2}`);
38335
+ for (const r3 of files.routers) lines.push(`- ${r3}`);
38238
38336
  lines.push("");
38239
38337
  lines.push(`### Pages (${files.pages.length})`);
38240
38338
  for (const p19 of files.pages) lines.push(`- ${p19}`);
@@ -39324,8 +39422,8 @@ function winManagerDirs(env) {
39324
39422
  function windowsAllowlistRoots(env) {
39325
39423
  const d2 = winManagerDirs(env);
39326
39424
  const roots = [];
39327
- for (const r2 of [d2.userprofile, d2.nvmHome, d2.nvmSymlink, d2.fnmDir, d2.voltaHome, d2.localappdata]) {
39328
- if (r2) roots.push(r2);
39425
+ for (const r3 of [d2.userprofile, d2.nvmHome, d2.nvmSymlink, d2.fnmDir, d2.voltaHome, d2.localappdata]) {
39426
+ if (r3) roots.push(r3);
39329
39427
  }
39330
39428
  roots.push("C:\\Program Files\\nodejs", "C:\\Program Files (x86)\\nodejs");
39331
39429
  return roots;