ctxloom-pro 1.5.1 → 1.5.3

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>
@@ -676,6 +676,47 @@ var ASTParser = class {
676
676
  processedIds.add(node.id);
677
677
  return;
678
678
  }
679
+ // ─── CommonJS require() calls ────────────────────────────────────
680
+ // ES6 import_statement above handles `import X from './foo'`. But
681
+ // pure CommonJS projects — express, most pre-2020 Node libs — use
682
+ // `require('./foo')` instead and never emit an import_statement
683
+ // node. Without this case, those projects produce a dependency
684
+ // graph with 0 edges and the blast-radius tool collapses to
685
+ // "just the seed file" because there's nothing to traverse.
686
+ //
687
+ // Mirrors the Ruby require pattern below (see the `case 'call'`
688
+ // branch later in the walker). Only literal-string arguments are
689
+ // resolvable; dynamic `require(varName)` is statically invisible
690
+ // and intentionally skipped.
691
+ //
692
+ // Patterns matched:
693
+ // require('./path')
694
+ // require('../path')
695
+ // require('./path.json') — JSON files emit edges too
696
+ // Patterns NOT matched:
697
+ // require(varName) — dynamic, can't resolve
698
+ // require.resolve('./path') — returns path string, not a load
699
+ // import('./path') — dynamic ESM, separate concern
700
+ case "call_expression": {
701
+ const fnNode = node.childForFieldName?.("function") ?? node.children.find((c) => c?.type === "identifier");
702
+ if (fnNode?.text === "require") {
703
+ const argsNode = node.childForFieldName?.("arguments") ?? node.children.find((c) => c?.type === "arguments");
704
+ const firstArg = argsNode?.children.find((c) => c?.type === "string");
705
+ if (firstArg) {
706
+ const spec = firstArg.text.replace(/^['"`]|['"`]$/g, "");
707
+ if (spec.length > 0) {
708
+ nodes.push({
709
+ type: "import",
710
+ name: spec,
711
+ source: spec,
712
+ startLine: node.startPosition.row + 1,
713
+ endLine: node.endPosition.row + 1
714
+ });
715
+ }
716
+ }
717
+ }
718
+ break;
719
+ }
679
720
  // ─── Export statements (export function, export default) ────────
680
721
  case "export_statement": {
681
722
  const hasDefault = node.children.some((c) => c?.type === "default");
@@ -804,6 +845,8 @@ var ASTParser = class {
804
845
  startLine: declarator.startPosition.row + 1,
805
846
  endLine: declarator.endPosition.row + 1
806
847
  });
848
+ } else if (valueNode.type === "call_expression") {
849
+ walk(valueNode);
807
850
  }
808
851
  }
809
852
  }
@@ -1956,29 +1999,54 @@ function resolveImport(fromAbs, raw, rootDir) {
1956
1999
  }
1957
2000
  function extractPythonImports(content) {
1958
2001
  const results = [];
1959
- const relFrom = /^from\s+(\.+[\w.]*)\s+import/gm;
1960
2002
  let m;
2003
+ const relFrom = /^from\s+(\.+[\w.]*)\s+import/gm;
1961
2004
  while ((m = relFrom.exec(content)) !== null) {
1962
2005
  results.push({ specifier: m[1], isRelative: true });
1963
2006
  }
2007
+ const absFrom = /^from\s+([A-Za-z_][\w.]*)\s+import/gm;
2008
+ while ((m = absFrom.exec(content)) !== null) {
2009
+ results.push({ specifier: m[1], isRelative: false });
2010
+ }
2011
+ const directImport = /^import\s+([A-Za-z_][\w.]*)/gm;
2012
+ while ((m = directImport.exec(content)) !== null) {
2013
+ results.push({ specifier: m[1], isRelative: false });
2014
+ }
1964
2015
  return results;
1965
2016
  }
1966
2017
  function resolvePythonImport(fromAbs, fromDir, raw, rootDir) {
1967
2018
  const dotsMatch = raw.specifier.match(/^(\.+)/);
1968
- const dots = dotsMatch?.[1] ?? ".";
1969
- const modulePart = raw.specifier.slice(dots.length);
1970
- let baseDir = fromDir;
1971
- for (let i = 1; i < dots.length; i++) {
1972
- baseDir = path5.dirname(baseDir);
1973
- }
1974
- const modulePath = modulePart.replace(/\./g, path5.sep);
1975
- const candidates = modulePath ? [
1976
- path5.join(baseDir, modulePath + ".py"),
1977
- path5.join(baseDir, modulePath, "__init__.py")
1978
- ] : [
1979
- path5.join(fromDir, "__init__.py"),
1980
- fromAbs
1981
- // self — skip below
2019
+ if (dotsMatch) {
2020
+ const dots = dotsMatch[1];
2021
+ const modulePart = raw.specifier.slice(dots.length);
2022
+ let baseDir = fromDir;
2023
+ for (let i = 1; i < dots.length; i++) {
2024
+ baseDir = path5.dirname(baseDir);
2025
+ }
2026
+ const modulePath2 = modulePart.replace(/\./g, path5.sep);
2027
+ const candidates2 = modulePath2 ? [
2028
+ path5.join(baseDir, modulePath2 + ".py"),
2029
+ path5.join(baseDir, modulePath2, "__init__.py")
2030
+ ] : [
2031
+ path5.join(fromDir, "__init__.py"),
2032
+ fromAbs
2033
+ // self — skip below
2034
+ ];
2035
+ for (const c of candidates2) {
2036
+ if (c !== fromAbs && fs5.existsSync(c)) {
2037
+ return path5.relative(rootDir, c);
2038
+ }
2039
+ }
2040
+ return null;
2041
+ }
2042
+ const modulePath = raw.specifier.replace(/\./g, path5.sep);
2043
+ const candidates = [
2044
+ // <repoRoot>/<package>/foo.py or <repoRoot>/<package>/foo/__init__.py
2045
+ path5.join(rootDir, modulePath + ".py"),
2046
+ path5.join(rootDir, modulePath, "__init__.py"),
2047
+ // src/ layout (common for Python apps that follow PEP 518 src-layout)
2048
+ path5.join(rootDir, "src", modulePath + ".py"),
2049
+ path5.join(rootDir, "src", modulePath, "__init__.py")
1982
2050
  ];
1983
2051
  for (const c of candidates) {
1984
2052
  if (c !== fromAbs && fs5.existsSync(c)) {
@@ -2913,10 +2981,28 @@ var DependencyGraph = class {
2913
2981
  }
2914
2982
  resolveImport(fromAbs, specifier, rootDir) {
2915
2983
  const dir = path7.dirname(fromAbs);
2916
- for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"]) {
2984
+ const extensions = [
2985
+ "",
2986
+ ".ts",
2987
+ ".tsx",
2988
+ ".js",
2989
+ ".jsx",
2990
+ ".mjs",
2991
+ ".cjs",
2992
+ "/index.ts",
2993
+ "/index.tsx",
2994
+ "/index.js",
2995
+ "/index.jsx",
2996
+ "/index.mjs",
2997
+ "/index.cjs"
2998
+ ];
2999
+ for (const ext of extensions) {
2917
3000
  const candidate = path7.resolve(dir, specifier.replace(/\.js$/, "") + ext);
2918
- if (fs7.existsSync(candidate)) {
2919
- return path7.relative(rootDir, candidate);
3001
+ try {
3002
+ if (fs7.statSync(candidate).isFile()) {
3003
+ return path7.relative(rootDir, candidate);
3004
+ }
3005
+ } catch {
2920
3006
  }
2921
3007
  }
2922
3008
  return null;
@@ -8297,6 +8383,60 @@ import fs16 from "fs";
8297
8383
  import os2 from "os";
8298
8384
  import path16 from "path";
8299
8385
  var DEFAULT_TELEMETRY_DIR = path16.join(os2.homedir(), ".ctxloom", "telemetry");
8386
+ function telemetryDir() {
8387
+ const raw = process.env.CTXLOOM_TELEMETRY_DIR ?? DEFAULT_TELEMETRY_DIR;
8388
+ if (raw.includes("..") || !path16.isAbsolute(raw)) {
8389
+ if (!telemetryDirWarned) {
8390
+ telemetryDirWarned = true;
8391
+ logger.warn('CTXLOOM_TELEMETRY_DIR rejected \u2014 must be an absolute path with no ".." segments; using default', {
8392
+ rejected: raw,
8393
+ fallback: DEFAULT_TELEMETRY_DIR
8394
+ });
8395
+ }
8396
+ return DEFAULT_TELEMETRY_DIR;
8397
+ }
8398
+ return path16.resolve(raw);
8399
+ }
8400
+ var telemetryDirWarned = false;
8401
+ function filenameForDate(date) {
8402
+ const y = date.getUTCFullYear();
8403
+ const m = String(date.getUTCMonth() + 1).padStart(2, "0");
8404
+ const d = String(date.getUTCDate()).padStart(2, "0");
8405
+ return `budget-events-${y}-${m}-${d}.jsonl`;
8406
+ }
8407
+ function readEvents(opts = {}) {
8408
+ const until = opts.until ?? /* @__PURE__ */ new Date();
8409
+ const since = opts.since ?? new Date(until.getTime() - 14 * 24 * 60 * 60 * 1e3);
8410
+ const dir = telemetryDir();
8411
+ if (!fs16.existsSync(dir)) return [];
8412
+ const out = [];
8413
+ 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)) {
8414
+ const file = path16.join(dir, filenameForDate(cursor));
8415
+ if (!fs16.existsSync(file)) continue;
8416
+ const text = fs16.readFileSync(file, "utf-8");
8417
+ for (const line of text.split("\n")) {
8418
+ if (line.trim() === "") continue;
8419
+ let parsed;
8420
+ try {
8421
+ parsed = JSON.parse(line);
8422
+ } catch {
8423
+ continue;
8424
+ }
8425
+ if (!isPersistedEvent(parsed)) continue;
8426
+ const eventTs = new Date(parsed.ts).getTime();
8427
+ if (!Number.isFinite(eventTs)) continue;
8428
+ if (eventTs < since.getTime() || eventTs > until.getTime()) continue;
8429
+ if (opts.tool && parsed.tool !== opts.tool) continue;
8430
+ out.push(parsed);
8431
+ }
8432
+ }
8433
+ return out;
8434
+ }
8435
+ function isPersistedEvent(v) {
8436
+ if (!v || typeof v !== "object") return false;
8437
+ const o = v;
8438
+ return typeof o.ts === "string" && typeof o.event === "string" && typeof o.tool === "string";
8439
+ }
8300
8440
 
8301
8441
  // ../../packages/core/src/budget/learnedSuggestions.ts
8302
8442
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -11452,7 +11592,7 @@ function resolveTelemetryLevel() {
11452
11592
  }
11453
11593
  var TELEMETRY_LEVEL = resolveTelemetryLevel();
11454
11594
  var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
11455
- var CTXLOOM_VERSION = "1.5.1".length > 0 ? "1.5.1" : "dev";
11595
+ var CTXLOOM_VERSION = "1.5.3".length > 0 ? "1.5.3" : "dev";
11456
11596
  var POSTHOG_HOST = "https://eu.i.posthog.com";
11457
11597
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
11458
11598
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -11784,6 +11924,69 @@ var SUPPORTED_HOST_IDS = HOST_ADAPTERS.map((h) => h.id);
11784
11924
  // ../../packages/core/src/install/hmacBlock.ts
11785
11925
  import crypto6 from "crypto";
11786
11926
 
11927
+ // ../../packages/core/src/utils/stats.ts
11928
+ function percentile2(values, p) {
11929
+ if (values.length === 0) return null;
11930
+ const sorted = [...values].sort((a, b) => a - b);
11931
+ const idx = Math.floor((sorted.length - 1) * p);
11932
+ return sorted[idx];
11933
+ }
11934
+
11935
+ // ../../packages/core/src/budget/budgetStats.ts
11936
+ function summarize(events, windowStart, windowEnd) {
11937
+ const byTool = /* @__PURE__ */ new Map();
11938
+ for (const e of events) {
11939
+ const existing = byTool.get(e.tool);
11940
+ if (existing) existing.push(e);
11941
+ else byTool.set(e.tool, [e]);
11942
+ }
11943
+ const fallbackTable = [];
11944
+ const distributionTable = [];
11945
+ for (const [tool, bucket] of byTool) {
11946
+ const fallbackUsed = bucket.filter((e) => e.event === "mcp.fallback.used");
11947
+ if (fallbackUsed.length > 0) {
11948
+ let skeleton = 0, truncate = 0, error = 0;
11949
+ for (const e of fallbackUsed) {
11950
+ const mode = typeof e.mode === "string" ? e.mode : "";
11951
+ if (mode === "skeleton" || mode === "skeleton+truncate") skeleton++;
11952
+ else if (mode === "truncate" || mode === "truncate-fallback") truncate++;
11953
+ else if (mode === "error") error++;
11954
+ }
11955
+ const total = skeleton + truncate + error;
11956
+ if (total > 0) {
11957
+ fallbackTable.push({
11958
+ tool,
11959
+ breaches: fallbackUsed.length,
11960
+ skeletonPct: Math.round(skeleton / total * 100),
11961
+ truncatePct: Math.round(truncate / total * 100),
11962
+ errorPct: Math.round(error / total * 100)
11963
+ });
11964
+ }
11965
+ }
11966
+ 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);
11967
+ if (tokens.length > 0) {
11968
+ distributionTable.push({
11969
+ tool,
11970
+ n: tokens.length,
11971
+ min: Math.min(...tokens),
11972
+ p50: percentile2(tokens, 0.5),
11973
+ p75: percentile2(tokens, 0.75),
11974
+ p95: percentile2(tokens, 0.95),
11975
+ max: Math.max(...tokens)
11976
+ });
11977
+ }
11978
+ }
11979
+ fallbackTable.sort((a, b) => a.tool.localeCompare(b.tool));
11980
+ distributionTable.sort((a, b) => a.tool.localeCompare(b.tool));
11981
+ return {
11982
+ windowStart: windowStart.toISOString(),
11983
+ windowEnd: windowEnd.toISOString(),
11984
+ totalEvents: events.length,
11985
+ fallbackTable,
11986
+ distributionTable
11987
+ };
11988
+ }
11989
+
11787
11990
  // server/loader.ts
11788
11991
  async function loadContext(root) {
11789
11992
  const absRoot = path40.resolve(root);
@@ -12362,6 +12565,59 @@ function buildTelemetryRouter() {
12362
12565
  return router;
12363
12566
  }
12364
12567
 
12568
+ // server/routes/budget-events.ts
12569
+ import { Router as Router14 } from "express";
12570
+ function parseWindowDays(raw) {
12571
+ if (!raw) return { days: 14 };
12572
+ const m = /^(\d+)d$/.exec(raw);
12573
+ if (!m) return { error: `Invalid --window "${raw}". Expected format: 1d, 7d, 14d, 30d` };
12574
+ const days = parseInt(m[1], 10);
12575
+ if (!Number.isFinite(days) || days <= 0) {
12576
+ return { error: `Invalid --window "${raw}". Must be a positive integer day count.` };
12577
+ }
12578
+ return { days };
12579
+ }
12580
+ function breachesPerDay(events) {
12581
+ const buckets = /* @__PURE__ */ new Map();
12582
+ for (const e of events) {
12583
+ if (e.event !== "mcp.budget.exceeded") continue;
12584
+ const day = e.ts.slice(0, 10);
12585
+ buckets.set(day, (buckets.get(day) ?? 0) + 1);
12586
+ }
12587
+ return Array.from(buckets.entries()).map(([day, count]) => ({ day, count })).sort((a, b) => a.day.localeCompare(b.day));
12588
+ }
12589
+ function buildBudgetEventsRouter() {
12590
+ const router = Router14();
12591
+ router.get("/", (req, res) => {
12592
+ const windowRaw = typeof req.query.window === "string" ? req.query.window : void 0;
12593
+ const toolFilter = typeof req.query.tool === "string" && req.query.tool.length > 0 ? req.query.tool : void 0;
12594
+ const parsed = parseWindowDays(windowRaw);
12595
+ if ("error" in parsed) {
12596
+ res.status(400).json({ error: parsed.error });
12597
+ return;
12598
+ }
12599
+ const { days } = parsed;
12600
+ const until = /* @__PURE__ */ new Date();
12601
+ const since = new Date(until.getTime() - days * 24 * 60 * 60 * 1e3);
12602
+ try {
12603
+ const events = readEvents({ since, until, tool: toolFilter });
12604
+ const summary = summarize(events, since, until);
12605
+ res.json({
12606
+ window: { since: since.toISOString(), until: until.toISOString(), days },
12607
+ totalEvents: summary.totalEvents,
12608
+ fallbackTable: summary.fallbackTable,
12609
+ distributionTable: summary.distributionTable,
12610
+ breachesPerDay: breachesPerDay(events)
12611
+ });
12612
+ } catch (err) {
12613
+ res.status(500).json({
12614
+ error: err instanceof Error ? err.message : String(err)
12615
+ });
12616
+ }
12617
+ });
12618
+ return router;
12619
+ }
12620
+
12365
12621
  // server/index.ts
12366
12622
  var __dirname2 = path46.dirname(fileURLToPath2(import.meta.url));
12367
12623
  async function startDashboard(options) {
@@ -12393,6 +12649,7 @@ async function startDashboard(options) {
12393
12649
  app.use("/api/tokens", buildTokensRouter(ctx));
12394
12650
  app.use("/api/trends", buildTrendsRouter(ctx));
12395
12651
  app.use("/api/trends", buildFileTrendsRouter(ctx));
12652
+ app.use("/api/budget-events", buildBudgetEventsRouter());
12396
12653
  app.get("/api/health", (_req, res) => res.json({ ok: true, gitEnabled: ctx.gitEnabled }));
12397
12654
  app.get("/api/status", (_req, res) => res.json({
12398
12655
  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
@@ -522,6 +522,47 @@ var ASTParser = class {
522
522
  processedIds.add(node.id);
523
523
  return;
524
524
  }
525
+ // ─── CommonJS require() calls ────────────────────────────────────
526
+ // ES6 import_statement above handles `import X from './foo'`. But
527
+ // pure CommonJS projects — express, most pre-2020 Node libs — use
528
+ // `require('./foo')` instead and never emit an import_statement
529
+ // node. Without this case, those projects produce a dependency
530
+ // graph with 0 edges and the blast-radius tool collapses to
531
+ // "just the seed file" because there's nothing to traverse.
532
+ //
533
+ // Mirrors the Ruby require pattern below (see the `case 'call'`
534
+ // branch later in the walker). Only literal-string arguments are
535
+ // resolvable; dynamic `require(varName)` is statically invisible
536
+ // and intentionally skipped.
537
+ //
538
+ // Patterns matched:
539
+ // require('./path')
540
+ // require('../path')
541
+ // require('./path.json') — JSON files emit edges too
542
+ // Patterns NOT matched:
543
+ // require(varName) — dynamic, can't resolve
544
+ // require.resolve('./path') — returns path string, not a load
545
+ // import('./path') — dynamic ESM, separate concern
546
+ case "call_expression": {
547
+ const fnNode = node.childForFieldName?.("function") ?? node.children.find((c) => c?.type === "identifier");
548
+ if (fnNode?.text === "require") {
549
+ const argsNode = node.childForFieldName?.("arguments") ?? node.children.find((c) => c?.type === "arguments");
550
+ const firstArg = argsNode?.children.find((c) => c?.type === "string");
551
+ if (firstArg) {
552
+ const spec = firstArg.text.replace(/^['"`]|['"`]$/g, "");
553
+ if (spec.length > 0) {
554
+ nodes.push({
555
+ type: "import",
556
+ name: spec,
557
+ source: spec,
558
+ startLine: node.startPosition.row + 1,
559
+ endLine: node.endPosition.row + 1
560
+ });
561
+ }
562
+ }
563
+ }
564
+ break;
565
+ }
525
566
  // ─── Export statements (export function, export default) ────────
526
567
  case "export_statement": {
527
568
  const hasDefault = node.children.some((c) => c?.type === "default");
@@ -650,6 +691,8 @@ var ASTParser = class {
650
691
  startLine: declarator.startPosition.row + 1,
651
692
  endLine: declarator.endPosition.row + 1
652
693
  });
694
+ } else if (valueNode.type === "call_expression") {
695
+ walk(valueNode);
653
696
  }
654
697
  }
655
698
  }
@@ -1798,29 +1841,54 @@ function resolveImport(fromAbs, raw, rootDir) {
1798
1841
  }
1799
1842
  function extractPythonImports(content) {
1800
1843
  const results = [];
1801
- const relFrom = /^from\s+(\.+[\w.]*)\s+import/gm;
1802
1844
  let m;
1845
+ const relFrom = /^from\s+(\.+[\w.]*)\s+import/gm;
1803
1846
  while ((m = relFrom.exec(content)) !== null) {
1804
1847
  results.push({ specifier: m[1], isRelative: true });
1805
1848
  }
1849
+ const absFrom = /^from\s+([A-Za-z_][\w.]*)\s+import/gm;
1850
+ while ((m = absFrom.exec(content)) !== null) {
1851
+ results.push({ specifier: m[1], isRelative: false });
1852
+ }
1853
+ const directImport = /^import\s+([A-Za-z_][\w.]*)/gm;
1854
+ while ((m = directImport.exec(content)) !== null) {
1855
+ results.push({ specifier: m[1], isRelative: false });
1856
+ }
1806
1857
  return results;
1807
1858
  }
1808
1859
  function resolvePythonImport(fromAbs, fromDir, raw, rootDir) {
1809
1860
  const dotsMatch = raw.specifier.match(/^(\.+)/);
1810
- const dots = dotsMatch?.[1] ?? ".";
1811
- const modulePart = raw.specifier.slice(dots.length);
1812
- let baseDir = fromDir;
1813
- for (let i = 1; i < dots.length; i++) {
1814
- baseDir = path4.dirname(baseDir);
1815
- }
1816
- const modulePath = modulePart.replace(/\./g, path4.sep);
1817
- const candidates = modulePath ? [
1818
- path4.join(baseDir, modulePath + ".py"),
1819
- path4.join(baseDir, modulePath, "__init__.py")
1820
- ] : [
1821
- path4.join(fromDir, "__init__.py"),
1822
- fromAbs
1823
- // self — skip below
1861
+ if (dotsMatch) {
1862
+ const dots = dotsMatch[1];
1863
+ const modulePart = raw.specifier.slice(dots.length);
1864
+ let baseDir = fromDir;
1865
+ for (let i = 1; i < dots.length; i++) {
1866
+ baseDir = path4.dirname(baseDir);
1867
+ }
1868
+ const modulePath2 = modulePart.replace(/\./g, path4.sep);
1869
+ const candidates2 = modulePath2 ? [
1870
+ path4.join(baseDir, modulePath2 + ".py"),
1871
+ path4.join(baseDir, modulePath2, "__init__.py")
1872
+ ] : [
1873
+ path4.join(fromDir, "__init__.py"),
1874
+ fromAbs
1875
+ // self — skip below
1876
+ ];
1877
+ for (const c of candidates2) {
1878
+ if (c !== fromAbs && fs4.existsSync(c)) {
1879
+ return path4.relative(rootDir, c);
1880
+ }
1881
+ }
1882
+ return null;
1883
+ }
1884
+ const modulePath = raw.specifier.replace(/\./g, path4.sep);
1885
+ const candidates = [
1886
+ // <repoRoot>/<package>/foo.py or <repoRoot>/<package>/foo/__init__.py
1887
+ path4.join(rootDir, modulePath + ".py"),
1888
+ path4.join(rootDir, modulePath, "__init__.py"),
1889
+ // src/ layout (common for Python apps that follow PEP 518 src-layout)
1890
+ path4.join(rootDir, "src", modulePath + ".py"),
1891
+ path4.join(rootDir, "src", modulePath, "__init__.py")
1824
1892
  ];
1825
1893
  for (const c of candidates) {
1826
1894
  if (c !== fromAbs && fs4.existsSync(c)) {
@@ -2755,10 +2823,28 @@ var DependencyGraph = class {
2755
2823
  }
2756
2824
  resolveImport(fromAbs, specifier, rootDir) {
2757
2825
  const dir = path6.dirname(fromAbs);
2758
- for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"]) {
2826
+ const extensions = [
2827
+ "",
2828
+ ".ts",
2829
+ ".tsx",
2830
+ ".js",
2831
+ ".jsx",
2832
+ ".mjs",
2833
+ ".cjs",
2834
+ "/index.ts",
2835
+ "/index.tsx",
2836
+ "/index.js",
2837
+ "/index.jsx",
2838
+ "/index.mjs",
2839
+ "/index.cjs"
2840
+ ];
2841
+ for (const ext of extensions) {
2759
2842
  const candidate = path6.resolve(dir, specifier.replace(/\.js$/, "") + ext);
2760
- if (fs6.existsSync(candidate)) {
2761
- return path6.relative(rootDir, candidate);
2843
+ try {
2844
+ if (fs6.statSync(candidate).isFile()) {
2845
+ return path6.relative(rootDir, candidate);
2846
+ }
2847
+ } catch {
2762
2848
  }
2763
2849
  }
2764
2850
  return null;
@@ -10205,7 +10291,7 @@ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
10205
10291
  function getTelemetryLevel() {
10206
10292
  return TELEMETRY_LEVEL;
10207
10293
  }
10208
- var CTXLOOM_VERSION = "1.5.1".length > 0 ? "1.5.1" : "dev";
10294
+ var CTXLOOM_VERSION = "1.5.3".length > 0 ? "1.5.3" : "dev";
10209
10295
  var POSTHOG_HOST = "https://eu.i.posthog.com";
10210
10296
  var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
10211
10297
  var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
@@ -11803,4 +11889,4 @@ export {
11803
11889
  skillFilePath,
11804
11890
  installHarness
11805
11891
  };
11806
- //# sourceMappingURL=chunk-D3RJQLX6.js.map
11892
+ //# sourceMappingURL=chunk-MIC7Q72C.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-D3RJQLX6.js";
48
+ } from "./chunk-MIC7Q72C.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.1".length > 0 ? "1.5.1" : "dev";
1023
+ var ctxloomVersion = "1.5.3".length > 0 ? "1.5.3" : "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-FU53WQZC.js");
1096
+ const { ApiClient } = await import("./src-MTMXJEKZ.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-FU53WQZC.js");
1474
+ const { installHarness } = await import("./src-MTMXJEKZ.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-FU53WQZC.js");
1537
+ const { validateAlias } = await import("./src-MTMXJEKZ.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-FU53WQZC.js");
1801
+ const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-MTMXJEKZ.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-FU53WQZC.js");
1825
+ const { DependencyGraph: DG } = await import("./src-MTMXJEKZ.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-FU53WQZC.js");
1834
+ const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-MTMXJEKZ.js");
1834
1835
  let parser;
1835
1836
  try {
1836
1837
  parser = new ASTParser2();