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.
- package/README.md +17 -2
- package/apps/dashboard/dist/dashboard/client/assets/index-C1BQXiWY.css +1 -0
- package/apps/dashboard/dist/dashboard/client/assets/index-CXHxBCR_.js +144 -0
- package/apps/dashboard/dist/dashboard/client/index.html +2 -2
- package/apps/dashboard/dist/server/index.js +276 -19
- package/dist/budgetStats-NCV473MV.js +11 -0
- package/dist/{budgetStats-TURA232F.js → chunk-7YSLFUVN.js} +4 -3
- package/dist/{chunk-D3RJQLX6.js → chunk-MIC7Q72C.js} +106 -20
- package/dist/index.js +14 -13
- package/dist/{src-FU53WQZC.js → src-MTMXJEKZ.js} +16 -3
- package/package.json +3 -1
- package/apps/dashboard/dist/dashboard/client/assets/index-DNFP0Mer.js +0 -144
- package/apps/dashboard/dist/dashboard/client/assets/index-MBoNDzdn.css +0 -1
|
@@ -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-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2919
|
-
|
|
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.
|
|
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(),
|
|
@@ -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
|
-
|
|
114
|
-
|
|
114
|
+
summarize,
|
|
115
|
+
renderSummary
|
|
115
116
|
};
|
|
116
|
-
//# sourceMappingURL=
|
|
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
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2761
|
-
|
|
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.
|
|
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-
|
|
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-
|
|
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.
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
1834
|
+
const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-MTMXJEKZ.js");
|
|
1834
1835
|
let parser;
|
|
1835
1836
|
try {
|
|
1836
1837
|
parser = new ASTParser2();
|