ctxloom-pro 1.5.0 → 1.5.2

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.
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>ctxloom dashboard</title>
7
- <script type="module" crossorigin src="/assets/index-DNFP0Mer.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-MBoNDzdn.css">
7
+ <script type="module" crossorigin src="/assets/index-CXHxBCR_.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-C1BQXiWY.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -8297,6 +8297,60 @@ import fs16 from "fs";
8297
8297
  import os2 from "os";
8298
8298
  import path16 from "path";
8299
8299
  var DEFAULT_TELEMETRY_DIR = path16.join(os2.homedir(), ".ctxloom", "telemetry");
8300
+ function telemetryDir() {
8301
+ const raw = process.env.CTXLOOM_TELEMETRY_DIR ?? DEFAULT_TELEMETRY_DIR;
8302
+ if (raw.includes("..") || !path16.isAbsolute(raw)) {
8303
+ if (!telemetryDirWarned) {
8304
+ telemetryDirWarned = true;
8305
+ logger.warn('CTXLOOM_TELEMETRY_DIR rejected \u2014 must be an absolute path with no ".." segments; using default', {
8306
+ rejected: raw,
8307
+ fallback: DEFAULT_TELEMETRY_DIR
8308
+ });
8309
+ }
8310
+ return DEFAULT_TELEMETRY_DIR;
8311
+ }
8312
+ return path16.resolve(raw);
8313
+ }
8314
+ var telemetryDirWarned = false;
8315
+ function filenameForDate(date) {
8316
+ const y = date.getUTCFullYear();
8317
+ const m = String(date.getUTCMonth() + 1).padStart(2, "0");
8318
+ const d = String(date.getUTCDate()).padStart(2, "0");
8319
+ return `budget-events-${y}-${m}-${d}.jsonl`;
8320
+ }
8321
+ function readEvents(opts = {}) {
8322
+ const until = opts.until ?? /* @__PURE__ */ new Date();
8323
+ const since = opts.since ?? new Date(until.getTime() - 14 * 24 * 60 * 60 * 1e3);
8324
+ const dir = telemetryDir();
8325
+ if (!fs16.existsSync(dir)) return [];
8326
+ const out = [];
8327
+ for (let cursor = new Date(Date.UTC(since.getUTCFullYear(), since.getUTCMonth(), since.getUTCDate())); cursor.getTime() <= until.getTime(); cursor = new Date(cursor.getTime() + 24 * 60 * 60 * 1e3)) {
8328
+ const file = path16.join(dir, filenameForDate(cursor));
8329
+ if (!fs16.existsSync(file)) continue;
8330
+ const text = fs16.readFileSync(file, "utf-8");
8331
+ for (const line of text.split("\n")) {
8332
+ if (line.trim() === "") continue;
8333
+ let parsed;
8334
+ try {
8335
+ parsed = JSON.parse(line);
8336
+ } catch {
8337
+ continue;
8338
+ }
8339
+ if (!isPersistedEvent(parsed)) continue;
8340
+ const eventTs = new Date(parsed.ts).getTime();
8341
+ if (!Number.isFinite(eventTs)) continue;
8342
+ if (eventTs < since.getTime() || eventTs > until.getTime()) continue;
8343
+ if (opts.tool && parsed.tool !== opts.tool) continue;
8344
+ out.push(parsed);
8345
+ }
8346
+ }
8347
+ return out;
8348
+ }
8349
+ function isPersistedEvent(v) {
8350
+ if (!v || typeof v !== "object") return false;
8351
+ const o = v;
8352
+ return typeof o.ts === "string" && typeof o.event === "string" && typeof o.tool === "string";
8353
+ }
8300
8354
 
8301
8355
  // ../../packages/core/src/budget/learnedSuggestions.ts
8302
8356
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -11452,7 +11506,7 @@ function resolveTelemetryLevel() {
11452
11506
  }
11453
11507
  var TELEMETRY_LEVEL = resolveTelemetryLevel();
11454
11508
  var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
11455
- var CTXLOOM_VERSION = "1.5.0".length > 0 ? "1.5.0" : "dev";
11509
+ var CTXLOOM_VERSION = "1.5.2".length > 0 ? "1.5.2" : "dev";
11456
11510
  var POSTHOG_HOST = "https://eu.i.posthog.com";
11457
11511
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
11458
11512
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -11784,6 +11838,69 @@ var SUPPORTED_HOST_IDS = HOST_ADAPTERS.map((h) => h.id);
11784
11838
  // ../../packages/core/src/install/hmacBlock.ts
11785
11839
  import crypto6 from "crypto";
11786
11840
 
11841
+ // ../../packages/core/src/utils/stats.ts
11842
+ function percentile2(values, p) {
11843
+ if (values.length === 0) return null;
11844
+ const sorted = [...values].sort((a, b) => a - b);
11845
+ const idx = Math.floor((sorted.length - 1) * p);
11846
+ return sorted[idx];
11847
+ }
11848
+
11849
+ // ../../packages/core/src/budget/budgetStats.ts
11850
+ function summarize(events, windowStart, windowEnd) {
11851
+ const byTool = /* @__PURE__ */ new Map();
11852
+ for (const e of events) {
11853
+ const existing = byTool.get(e.tool);
11854
+ if (existing) existing.push(e);
11855
+ else byTool.set(e.tool, [e]);
11856
+ }
11857
+ const fallbackTable = [];
11858
+ const distributionTable = [];
11859
+ for (const [tool, bucket] of byTool) {
11860
+ const fallbackUsed = bucket.filter((e) => e.event === "mcp.fallback.used");
11861
+ if (fallbackUsed.length > 0) {
11862
+ let skeleton = 0, truncate = 0, error = 0;
11863
+ for (const e of fallbackUsed) {
11864
+ const mode = typeof e.mode === "string" ? e.mode : "";
11865
+ if (mode === "skeleton" || mode === "skeleton+truncate") skeleton++;
11866
+ else if (mode === "truncate" || mode === "truncate-fallback") truncate++;
11867
+ else if (mode === "error") error++;
11868
+ }
11869
+ const total = skeleton + truncate + error;
11870
+ if (total > 0) {
11871
+ fallbackTable.push({
11872
+ tool,
11873
+ breaches: fallbackUsed.length,
11874
+ skeletonPct: Math.round(skeleton / total * 100),
11875
+ truncatePct: Math.round(truncate / total * 100),
11876
+ errorPct: Math.round(error / total * 100)
11877
+ });
11878
+ }
11879
+ }
11880
+ const tokens = bucket.filter((e) => e.event === "mcp.budget.exceeded").map((e) => typeof e.original_tokens === "number" ? e.original_tokens : null).filter((n) => n !== null);
11881
+ if (tokens.length > 0) {
11882
+ distributionTable.push({
11883
+ tool,
11884
+ n: tokens.length,
11885
+ min: Math.min(...tokens),
11886
+ p50: percentile2(tokens, 0.5),
11887
+ p75: percentile2(tokens, 0.75),
11888
+ p95: percentile2(tokens, 0.95),
11889
+ max: Math.max(...tokens)
11890
+ });
11891
+ }
11892
+ }
11893
+ fallbackTable.sort((a, b) => a.tool.localeCompare(b.tool));
11894
+ distributionTable.sort((a, b) => a.tool.localeCompare(b.tool));
11895
+ return {
11896
+ windowStart: windowStart.toISOString(),
11897
+ windowEnd: windowEnd.toISOString(),
11898
+ totalEvents: events.length,
11899
+ fallbackTable,
11900
+ distributionTable
11901
+ };
11902
+ }
11903
+
11787
11904
  // server/loader.ts
11788
11905
  async function loadContext(root) {
11789
11906
  const absRoot = path40.resolve(root);
@@ -12362,6 +12479,59 @@ function buildTelemetryRouter() {
12362
12479
  return router;
12363
12480
  }
12364
12481
 
12482
+ // server/routes/budget-events.ts
12483
+ import { Router as Router14 } from "express";
12484
+ function parseWindowDays(raw) {
12485
+ if (!raw) return { days: 14 };
12486
+ const m = /^(\d+)d$/.exec(raw);
12487
+ if (!m) return { error: `Invalid --window "${raw}". Expected format: 1d, 7d, 14d, 30d` };
12488
+ const days = parseInt(m[1], 10);
12489
+ if (!Number.isFinite(days) || days <= 0) {
12490
+ return { error: `Invalid --window "${raw}". Must be a positive integer day count.` };
12491
+ }
12492
+ return { days };
12493
+ }
12494
+ function breachesPerDay(events) {
12495
+ const buckets = /* @__PURE__ */ new Map();
12496
+ for (const e of events) {
12497
+ if (e.event !== "mcp.budget.exceeded") continue;
12498
+ const day = e.ts.slice(0, 10);
12499
+ buckets.set(day, (buckets.get(day) ?? 0) + 1);
12500
+ }
12501
+ return Array.from(buckets.entries()).map(([day, count]) => ({ day, count })).sort((a, b) => a.day.localeCompare(b.day));
12502
+ }
12503
+ function buildBudgetEventsRouter() {
12504
+ const router = Router14();
12505
+ router.get("/", (req, res) => {
12506
+ const windowRaw = typeof req.query.window === "string" ? req.query.window : void 0;
12507
+ const toolFilter = typeof req.query.tool === "string" && req.query.tool.length > 0 ? req.query.tool : void 0;
12508
+ const parsed = parseWindowDays(windowRaw);
12509
+ if ("error" in parsed) {
12510
+ res.status(400).json({ error: parsed.error });
12511
+ return;
12512
+ }
12513
+ const { days } = parsed;
12514
+ const until = /* @__PURE__ */ new Date();
12515
+ const since = new Date(until.getTime() - days * 24 * 60 * 60 * 1e3);
12516
+ try {
12517
+ const events = readEvents({ since, until, tool: toolFilter });
12518
+ const summary = summarize(events, since, until);
12519
+ res.json({
12520
+ window: { since: since.toISOString(), until: until.toISOString(), days },
12521
+ totalEvents: summary.totalEvents,
12522
+ fallbackTable: summary.fallbackTable,
12523
+ distributionTable: summary.distributionTable,
12524
+ breachesPerDay: breachesPerDay(events)
12525
+ });
12526
+ } catch (err) {
12527
+ res.status(500).json({
12528
+ error: err instanceof Error ? err.message : String(err)
12529
+ });
12530
+ }
12531
+ });
12532
+ return router;
12533
+ }
12534
+
12365
12535
  // server/index.ts
12366
12536
  var __dirname2 = path46.dirname(fileURLToPath2(import.meta.url));
12367
12537
  async function startDashboard(options) {
@@ -12393,6 +12563,7 @@ async function startDashboard(options) {
12393
12563
  app.use("/api/tokens", buildTokensRouter(ctx));
12394
12564
  app.use("/api/trends", buildTrendsRouter(ctx));
12395
12565
  app.use("/api/trends", buildFileTrendsRouter(ctx));
12566
+ app.use("/api/budget-events", buildBudgetEventsRouter());
12396
12567
  app.get("/api/health", (_req, res) => res.json({ ok: true, gitEnabled: ctx.gitEnabled }));
12397
12568
  app.get("/api/status", (_req, res) => res.json({
12398
12569
  lastIndexed: ctx.lastIndexed.toISOString(),
@@ -0,0 +1,11 @@
1
+ import {
2
+ percentile,
3
+ renderSummary,
4
+ summarize
5
+ } from "./chunk-7YSLFUVN.js";
6
+ export {
7
+ percentile,
8
+ renderSummary,
9
+ summarize
10
+ };
11
+ //# sourceMappingURL=budgetStats-NCV473MV.js.map
@@ -108,9 +108,10 @@ function renderSummary(s) {
108
108
  function fmt(n) {
109
109
  return n === null ? " \u2014" : String(n).padStart(6);
110
110
  }
111
+
111
112
  export {
112
113
  percentile,
113
- renderSummary,
114
- summarize
114
+ summarize,
115
+ renderSummary
115
116
  };
116
- //# sourceMappingURL=budgetStats-TURA232F.js.map
117
+ //# sourceMappingURL=chunk-7YSLFUVN.js.map
@@ -4633,7 +4633,7 @@ function learnSuggestionsFromTelemetry(opts = {}) {
4633
4633
  const tok = raw.original_tokens;
4634
4634
  if (typeof tok === "number" && Number.isFinite(tok)) {
4635
4635
  const acc = tokenSums.get(raw.tool) ?? { sum: 0, n: 0 };
4636
- acc.sum += tok;
4636
+ acc.sum += clampTokens(tok);
4637
4637
  acc.n += 1;
4638
4638
  tokenSums.set(raw.tool, acc);
4639
4639
  }
@@ -10205,7 +10205,7 @@ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
10205
10205
  function getTelemetryLevel() {
10206
10206
  return TELEMETRY_LEVEL;
10207
10207
  }
10208
- var CTXLOOM_VERSION = "1.5.0".length > 0 ? "1.5.0" : "dev";
10208
+ var CTXLOOM_VERSION = "1.5.2".length > 0 ? "1.5.2" : "dev";
10209
10209
  var POSTHOG_HOST = "https://eu.i.posthog.com";
10210
10210
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
10211
10211
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -11507,7 +11507,10 @@ function resolveExtraHosts(ids, warnings) {
11507
11507
  function safeJoin(root, name) {
11508
11508
  const target = path36.resolve(root, name);
11509
11509
  const rootResolved = path36.resolve(root);
11510
- if (!target.startsWith(rootResolved + path36.sep) && target !== rootResolved) {
11510
+ const caseFold = process.platform === "darwin" || process.platform === "win32";
11511
+ const t = caseFold ? target.toLowerCase() : target;
11512
+ const r = caseFold ? rootResolved.toLowerCase() : rootResolved;
11513
+ if (!t.startsWith(r + path36.sep) && t !== r) {
11511
11514
  throw new Error(`installHarness: refusing to write outside project root: ${target}`);
11512
11515
  }
11513
11516
  return target;
@@ -11800,4 +11803,4 @@ export {
11800
11803
  skillFilePath,
11801
11804
  installHarness
11802
11805
  };
11803
- //# sourceMappingURL=chunk-J2NLNQ4I.js.map
11806
+ //# sourceMappingURL=chunk-SMI35T32.js.map
package/dist/index.js CHANGED
@@ -1,8 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- addCtxloomToConfig,
4
- detectInstalledClients
5
- } from "./chunk-II2DPYRJ.js";
6
2
  import {
7
3
  ASTParser,
8
4
  AuthorResolver,
@@ -49,7 +45,11 @@ import {
49
45
  validateDefaultRoot,
50
46
  wrapWithIndexingEnvelope,
51
47
  writeCODEOWNERS
52
- } from "./chunk-J2NLNQ4I.js";
48
+ } from "./chunk-SMI35T32.js";
49
+ import {
50
+ addCtxloomToConfig,
51
+ detectInstalledClients
52
+ } from "./chunk-II2DPYRJ.js";
53
53
  import {
54
54
  VectorStore
55
55
  } from "./chunk-DVI2RWJR.js";
@@ -61,6 +61,7 @@ import "./chunk-5I6CJITG.js";
61
61
  import {
62
62
  logger
63
63
  } from "./chunk-TYDMSHV7.js";
64
+ import "./chunk-7YSLFUVN.js";
64
65
 
65
66
  // src/server.ts
66
67
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -1019,7 +1020,7 @@ try {
1019
1020
  } catch {
1020
1021
  }
1021
1022
  var args = process.argv.slice(2);
1022
- var ctxloomVersion = "1.5.0".length > 0 ? "1.5.0" : "dev";
1023
+ var ctxloomVersion = "1.5.2".length > 0 ? "1.5.2" : "dev";
1023
1024
  if (args.includes("--version") || args.includes("-v")) {
1024
1025
  process.stdout.write(`ctxloom ${ctxloomVersion}
1025
1026
  `);
@@ -1092,7 +1093,7 @@ async function checkLicense() {
1092
1093
  if (command !== void 0 && LICENSE_GATE_BYPASS_COMMANDS.has(command)) return;
1093
1094
  const ciKey = process.env["CTXLOOM_LICENSE_KEY"];
1094
1095
  if (ciKey) {
1095
- const { ApiClient } = await import("./src-PVBWVQMM.js");
1096
+ const { ApiClient } = await import("./src-AHIZ4UNS.js");
1096
1097
  const client = new ApiClient(process.env["CTXLOOM_API_BASE"]);
1097
1098
  try {
1098
1099
  const result = await client.validate(ciKey, "ci-ephemeral");
@@ -1470,7 +1471,7 @@ async function main() {
1470
1471
  }
1471
1472
  if (!skipHarness) {
1472
1473
  process.stdout.write("\n");
1473
- const { installHarness } = await import("./src-PVBWVQMM.js");
1474
+ const { installHarness } = await import("./src-AHIZ4UNS.js");
1474
1475
  const h = installHarness({ cwd: initRoot, dryRun, force, extraHosts });
1475
1476
  const harnessFiles = [
1476
1477
  h.claudeMd,
@@ -1533,7 +1534,7 @@ async function main() {
1533
1534
  process.exit(1);
1534
1535
  }
1535
1536
  if (alias !== void 0) {
1536
- const { validateAlias } = await import("./src-PVBWVQMM.js");
1537
+ const { validateAlias } = await import("./src-AHIZ4UNS.js");
1537
1538
  const v = validateAlias(alias);
1538
1539
  if (!v.ok) {
1539
1540
  console.error(`[ctxloom] Invalid alias: ${v.reason}`);
@@ -1617,7 +1618,7 @@ async function main() {
1617
1618
  const until = /* @__PURE__ */ new Date();
1618
1619
  const since = new Date(until.getTime() - days * 24 * 60 * 60 * 1e3);
1619
1620
  const { readEvents } = await import("./eventCollector-QSRBVUDF.js");
1620
- const { summarize, renderSummary } = await import("./budgetStats-TURA232F.js");
1621
+ const { summarize, renderSummary } = await import("./budgetStats-NCV473MV.js");
1621
1622
  const events = readEvents({ since, until, tool: toolArg });
1622
1623
  const summary = summarize(events, since, until);
1623
1624
  console.log(renderSummary(summary));
@@ -1797,7 +1798,7 @@ Suggested reviewers for ${files.length} file(s):`);
1797
1798
  process.stderr.write("[ctxloom] --limit must be a non-negative integer (0 for unlimited)\n");
1798
1799
  process.exit(2);
1799
1800
  }
1800
- const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-PVBWVQMM.js");
1801
+ const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-AHIZ4UNS.js");
1801
1802
  let config;
1802
1803
  try {
1803
1804
  config = await loadRulesConfig(root);
@@ -1821,7 +1822,7 @@ Suggested reviewers for ${files.length} file(s):`);
1821
1822
  }
1822
1823
  let graph;
1823
1824
  if (useSnapshot) {
1824
- const { DependencyGraph: DG } = await import("./src-PVBWVQMM.js");
1825
+ const { DependencyGraph: DG } = await import("./src-AHIZ4UNS.js");
1825
1826
  graph = new DG();
1826
1827
  const loaded = await graph.loadSnapshotOnly(root);
1827
1828
  if (!loaded) {
@@ -1830,7 +1831,7 @@ Suggested reviewers for ${files.length} file(s):`);
1830
1831
  }
1831
1832
  } else {
1832
1833
  process.stderr.write("[ctxloom] Building dependency graph...\n");
1833
- const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-PVBWVQMM.js");
1834
+ const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-AHIZ4UNS.js");
1834
1835
  let parser;
1835
1836
  try {
1836
1837
  parser = new ASTParser2();
@@ -129,7 +129,7 @@ import {
129
129
  wrapBlock,
130
130
  wrapWithIndexingEnvelope,
131
131
  writeCODEOWNERS
132
- } from "./chunk-J2NLNQ4I.js";
132
+ } from "./chunk-SMI35T32.js";
133
133
  import {
134
134
  VectorStore
135
135
  } from "./chunk-DVI2RWJR.js";
@@ -139,10 +139,18 @@ import {
139
139
  generateEmbedding,
140
140
  indexDirectory
141
141
  } from "./chunk-UVR65QBJ.js";
142
- import "./chunk-5I6CJITG.js";
142
+ import {
143
+ filenameForDate,
144
+ readEvents,
145
+ telemetryDir
146
+ } from "./chunk-5I6CJITG.js";
143
147
  import {
144
148
  logger
145
149
  } from "./chunk-TYDMSHV7.js";
150
+ import {
151
+ renderSummary,
152
+ summarize
153
+ } from "./chunk-7YSLFUVN.js";
146
154
  export {
147
155
  ASTParser,
148
156
  ApiClient,
@@ -223,6 +231,7 @@ export {
223
231
  extractImports,
224
232
  extractNotebookLanguage,
225
233
  extractNotebookPythonSource,
234
+ filenameForDate,
226
235
  findGrammar,
227
236
  findGrammarByExtension,
228
237
  findLargeFunctions,
@@ -256,7 +265,9 @@ export {
256
265
  noParseableSourcesWarning,
257
266
  projectRootNotFoundError,
258
267
  projectRootUnreadableError,
268
+ readEvents,
259
269
  recordTrendSnapshot,
270
+ renderSummary as renderBudgetSummary,
260
271
  renderStatusXml,
261
272
  requireActive,
262
273
  resolveHmacKey,
@@ -272,6 +283,8 @@ export {
272
283
  shouldShowTelemetryNotice,
273
284
  skillFilePath,
274
285
  startTrial,
286
+ summarize as summarizeBudgetEvents,
287
+ telemetryDir,
275
288
  track,
276
289
  upsertBlock,
277
290
  validateAlias,
@@ -281,4 +294,4 @@ export {
281
294
  wrapWithIndexingEnvelope,
282
295
  writeCODEOWNERS
283
296
  };
284
- //# sourceMappingURL=src-PVBWVQMM.js.map
297
+ //# sourceMappingURL=src-AHIZ4UNS.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxloom-pro",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "ctxloom — The Universal Code Context Engine. A local-first MCP server providing intelligent code context via hybrid Vector + AST + Graph search with Skeletonization (92% token reduction).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",