ctxloom-pro 1.3.0 → 1.3.1
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 +6 -1
- package/apps/dashboard/dist/server/index.js +107 -100
- package/dist/budgetStats-TURA232F.js +116 -0
- package/dist/chunk-5I6CJITG.js +99 -0
- package/dist/{chunk-Q2KTZNNU.js → chunk-TIYTPWYN.js} +29 -9
- package/dist/eventCollector-QSRBVUDF.js +18 -0
- package/dist/index.js +29 -8
- package/dist/{src-HNXOOOWF.js → src-KTFHRVTO.js} +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,8 @@ The full first-run flow is **one install + one trial + one init per project.** E
|
|
|
47
47
|
npm install -g ctxloom-pro
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
> **For local trial / dev use the unpinned command above is fine.** For unattended CI usage, pin to the exact version (`ctxloom-pro@1.3.1`) so future CLI releases don't silently desync your agent-spec coverage — see the workflow example below.
|
|
51
|
+
|
|
50
52
|
### 2 — Start your free trial (once per email)
|
|
51
53
|
|
|
52
54
|
```bash
|
|
@@ -341,7 +343,10 @@ jobs:
|
|
|
341
343
|
- uses: actions/checkout@v4
|
|
342
344
|
- uses: actions/setup-node@v4
|
|
343
345
|
with: { node-version: '20' }
|
|
344
|
-
|
|
346
|
+
# Exact pin (not `@^1`) so future CLI releases that add/remove MCP
|
|
347
|
+
# tools don't silently desync your reviewer-agent specs. Bump on
|
|
348
|
+
# every release; see CHANGELOG.md for the live version table.
|
|
349
|
+
- run: npm install -g ctxloom-pro@1.3.1
|
|
345
350
|
- run: ctxloom index
|
|
346
351
|
- run: ctxloom rules check --json
|
|
347
352
|
```
|
|
@@ -166,12 +166,12 @@ var init_VectorStore = __esm({
|
|
|
166
166
|
// server/index.ts
|
|
167
167
|
import express from "express";
|
|
168
168
|
import cors from "cors";
|
|
169
|
-
import
|
|
170
|
-
import
|
|
169
|
+
import path45 from "path";
|
|
170
|
+
import fs33 from "fs";
|
|
171
171
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
172
172
|
|
|
173
173
|
// server/loader.ts
|
|
174
|
-
import
|
|
174
|
+
import path39 from "path";
|
|
175
175
|
|
|
176
176
|
// ../../packages/core/src/graph/DependencyGraph.ts
|
|
177
177
|
import fs7 from "fs";
|
|
@@ -3181,8 +3181,8 @@ var CoChangeIndex = class _CoChangeIndex {
|
|
|
3181
3181
|
if (event.isBulk || event.isMerge) return;
|
|
3182
3182
|
const paths = event.files.map((f) => f.path);
|
|
3183
3183
|
if (paths.length === 0) return;
|
|
3184
|
-
for (const
|
|
3185
|
-
this.nodeCounts.set(
|
|
3184
|
+
for (const path46 of paths) {
|
|
3185
|
+
this.nodeCounts.set(path46, (this.nodeCounts.get(path46) ?? 0) + 1);
|
|
3186
3186
|
}
|
|
3187
3187
|
for (let i = 0; i < paths.length; i++) {
|
|
3188
3188
|
for (let j = i + 1; j < paths.length; j++) {
|
|
@@ -3329,8 +3329,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3329
3329
|
*/
|
|
3330
3330
|
snapshot() {
|
|
3331
3331
|
const nodes = {};
|
|
3332
|
-
for (const [
|
|
3333
|
-
nodes[
|
|
3332
|
+
for (const [path46, raw] of this.nodes) {
|
|
3333
|
+
nodes[path46] = {
|
|
3334
3334
|
commits: raw.commits,
|
|
3335
3335
|
churnLines: raw.churnLines,
|
|
3336
3336
|
bugCommits: raw.bugCommits,
|
|
@@ -3345,8 +3345,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3345
3345
|
*/
|
|
3346
3346
|
static load(s) {
|
|
3347
3347
|
const idx = new _ChurnIndex();
|
|
3348
|
-
for (const [
|
|
3349
|
-
idx.nodes.set(
|
|
3348
|
+
for (const [path46, raw] of Object.entries(s.nodes)) {
|
|
3349
|
+
idx.nodes.set(path46, {
|
|
3350
3350
|
commits: raw.commits,
|
|
3351
3351
|
churnLines: raw.churnLines,
|
|
3352
3352
|
bugCommits: raw.bugCommits,
|
|
@@ -3359,8 +3359,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3359
3359
|
// -------------------------------------------------------------------------
|
|
3360
3360
|
// Private helpers
|
|
3361
3361
|
// -------------------------------------------------------------------------
|
|
3362
|
-
getOrCreate(
|
|
3363
|
-
const existing = this.nodes.get(
|
|
3362
|
+
getOrCreate(path46) {
|
|
3363
|
+
const existing = this.nodes.get(path46);
|
|
3364
3364
|
if (existing !== void 0) return existing;
|
|
3365
3365
|
const fresh = {
|
|
3366
3366
|
commits: 0,
|
|
@@ -3369,7 +3369,7 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3369
3369
|
authorCounts: {},
|
|
3370
3370
|
lastTouch: 0
|
|
3371
3371
|
};
|
|
3372
|
-
this.nodes.set(
|
|
3372
|
+
this.nodes.set(path46, fresh);
|
|
3373
3373
|
return fresh;
|
|
3374
3374
|
}
|
|
3375
3375
|
};
|
|
@@ -3450,12 +3450,12 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3450
3450
|
*/
|
|
3451
3451
|
snapshot() {
|
|
3452
3452
|
const nodes = {};
|
|
3453
|
-
for (const [
|
|
3453
|
+
for (const [path46, raw] of this.nodes) {
|
|
3454
3454
|
const authorWeights = {};
|
|
3455
3455
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3456
3456
|
authorWeights[email] = { ...entry };
|
|
3457
3457
|
}
|
|
3458
|
-
nodes[
|
|
3458
|
+
nodes[path46] = { authorWeights, lastTouch: raw.lastTouch };
|
|
3459
3459
|
}
|
|
3460
3460
|
return { version: 1, nodes };
|
|
3461
3461
|
}
|
|
@@ -3464,23 +3464,23 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3464
3464
|
*/
|
|
3465
3465
|
static load(s) {
|
|
3466
3466
|
const idx = new _OwnershipIndex();
|
|
3467
|
-
for (const [
|
|
3467
|
+
for (const [path46, raw] of Object.entries(s.nodes)) {
|
|
3468
3468
|
const authorWeights = {};
|
|
3469
3469
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3470
3470
|
authorWeights[email] = { ...entry };
|
|
3471
3471
|
}
|
|
3472
|
-
idx.nodes.set(
|
|
3472
|
+
idx.nodes.set(path46, { authorWeights, lastTouch: raw.lastTouch });
|
|
3473
3473
|
}
|
|
3474
3474
|
return idx;
|
|
3475
3475
|
}
|
|
3476
3476
|
// -------------------------------------------------------------------------
|
|
3477
3477
|
// Private helpers
|
|
3478
3478
|
// -------------------------------------------------------------------------
|
|
3479
|
-
getOrCreate(
|
|
3480
|
-
const existing = this.nodes.get(
|
|
3479
|
+
getOrCreate(path46) {
|
|
3480
|
+
const existing = this.nodes.get(path46);
|
|
3481
3481
|
if (existing !== void 0) return existing;
|
|
3482
3482
|
const fresh = { authorWeights: {}, lastTouch: 0 };
|
|
3483
|
-
this.nodes.set(
|
|
3483
|
+
this.nodes.set(path46, fresh);
|
|
3484
3484
|
return fresh;
|
|
3485
3485
|
}
|
|
3486
3486
|
};
|
|
@@ -4718,8 +4718,8 @@ function getErrorMap() {
|
|
|
4718
4718
|
|
|
4719
4719
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
4720
4720
|
var makeIssue = (params) => {
|
|
4721
|
-
const { data, path:
|
|
4722
|
-
const fullPath = [...
|
|
4721
|
+
const { data, path: path46, errorMaps, issueData } = params;
|
|
4722
|
+
const fullPath = [...path46, ...issueData.path || []];
|
|
4723
4723
|
const fullIssue = {
|
|
4724
4724
|
...issueData,
|
|
4725
4725
|
path: fullPath
|
|
@@ -4835,11 +4835,11 @@ var errorUtil;
|
|
|
4835
4835
|
|
|
4836
4836
|
// ../../node_modules/zod/v3/types.js
|
|
4837
4837
|
var ParseInputLazyPath = class {
|
|
4838
|
-
constructor(parent, value,
|
|
4838
|
+
constructor(parent, value, path46, key) {
|
|
4839
4839
|
this._cachedPath = [];
|
|
4840
4840
|
this.parent = parent;
|
|
4841
4841
|
this.data = value;
|
|
4842
|
-
this._path =
|
|
4842
|
+
this._path = path46;
|
|
4843
4843
|
this._key = key;
|
|
4844
4844
|
}
|
|
4845
4845
|
get path() {
|
|
@@ -8294,6 +8294,13 @@ init_embedder();
|
|
|
8294
8294
|
// ../../packages/core/src/budget/budget.ts
|
|
8295
8295
|
init_logger();
|
|
8296
8296
|
|
|
8297
|
+
// ../../packages/core/src/budget/eventCollector.ts
|
|
8298
|
+
init_logger();
|
|
8299
|
+
import fs16 from "fs";
|
|
8300
|
+
import os2 from "os";
|
|
8301
|
+
import path16 from "path";
|
|
8302
|
+
var DEFAULT_TELEMETRY_DIR = path16.join(os2.homedir(), ".ctxloom", "telemetry");
|
|
8303
|
+
|
|
8297
8304
|
// ../../packages/core/src/tools/search.ts
|
|
8298
8305
|
var Schema = external_exports.object({
|
|
8299
8306
|
query: external_exports.string().describe("Search query \u2014 natural language or code fragment"),
|
|
@@ -8316,7 +8323,7 @@ var Schema2 = external_exports.object({
|
|
|
8316
8323
|
});
|
|
8317
8324
|
|
|
8318
8325
|
// ../../packages/core/src/tools/context-packet.ts
|
|
8319
|
-
import
|
|
8326
|
+
import path17 from "path";
|
|
8320
8327
|
var Schema3 = external_exports.object({
|
|
8321
8328
|
target_file: external_exports.string().describe("Relative path to the primary file"),
|
|
8322
8329
|
mode: external_exports.enum(["edit", "read"]).optional().default("edit").describe("Context mode"),
|
|
@@ -8328,7 +8335,7 @@ var Schema3 = external_exports.object({
|
|
|
8328
8335
|
});
|
|
8329
8336
|
|
|
8330
8337
|
// ../../packages/core/src/tools/findCallers.ts
|
|
8331
|
-
import
|
|
8338
|
+
import path18 from "path";
|
|
8332
8339
|
|
|
8333
8340
|
// ../../packages/core/src/tools/call-graph.ts
|
|
8334
8341
|
var Schema4 = external_exports.object({
|
|
@@ -8429,7 +8436,7 @@ var Schema12 = external_exports.object({
|
|
|
8429
8436
|
});
|
|
8430
8437
|
|
|
8431
8438
|
// ../../packages/core/src/tools/knowledge-gaps.ts
|
|
8432
|
-
import
|
|
8439
|
+
import path19 from "path";
|
|
8433
8440
|
var Schema13 = external_exports.object({
|
|
8434
8441
|
min_importers: external_exports.number().min(1).max(50).optional().default(3).describe(
|
|
8435
8442
|
"Minimum importers to qualify as an untested hub (default: 3)"
|
|
@@ -8458,7 +8465,7 @@ var Schema14 = external_exports.object({
|
|
|
8458
8465
|
});
|
|
8459
8466
|
|
|
8460
8467
|
// ../../packages/core/src/tools/wiki-generate.ts
|
|
8461
|
-
import
|
|
8468
|
+
import fs17 from "fs";
|
|
8462
8469
|
var Schema15 = external_exports.object({
|
|
8463
8470
|
force: external_exports.boolean().optional().default(false).describe(
|
|
8464
8471
|
"Regenerate all pages even if content unchanged (default: false)"
|
|
@@ -8506,8 +8513,8 @@ var Schema17 = external_exports.object({
|
|
|
8506
8513
|
});
|
|
8507
8514
|
|
|
8508
8515
|
// ../../packages/core/src/tools/refactor-preview.ts
|
|
8509
|
-
import
|
|
8510
|
-
import
|
|
8516
|
+
import fs18 from "fs";
|
|
8517
|
+
import path20 from "path";
|
|
8511
8518
|
var Schema18 = external_exports.object({
|
|
8512
8519
|
symbol: external_exports.string().min(1).describe("Symbol name to rename (exact match, case-sensitive)"),
|
|
8513
8520
|
new_name: external_exports.string().min(1).describe("New name for the symbol"),
|
|
@@ -8542,8 +8549,8 @@ var Schema19 = external_exports.object({
|
|
|
8542
8549
|
init_embedder();
|
|
8543
8550
|
init_VectorStore();
|
|
8544
8551
|
init_logger();
|
|
8545
|
-
import
|
|
8546
|
-
import
|
|
8552
|
+
import fs19 from "fs";
|
|
8553
|
+
import path21 from "path";
|
|
8547
8554
|
var Schema20 = external_exports.object({
|
|
8548
8555
|
query: external_exports.string().min(1).describe("Search query \u2014 natural language or code fragment"),
|
|
8549
8556
|
limit: external_exports.number().min(1).max(100).optional().default(10).describe(
|
|
@@ -8560,8 +8567,8 @@ var Schema20 = external_exports.object({
|
|
|
8560
8567
|
});
|
|
8561
8568
|
|
|
8562
8569
|
// ../../packages/core/src/tools/apply-refactor.ts
|
|
8563
|
-
import
|
|
8564
|
-
import
|
|
8570
|
+
import fs20 from "fs";
|
|
8571
|
+
import path22 from "path";
|
|
8565
8572
|
var Schema21 = external_exports.object({
|
|
8566
8573
|
symbol: external_exports.string().min(1).describe("Symbol name to rename (exact, case-sensitive)"),
|
|
8567
8574
|
new_name: external_exports.string().min(1).describe("New name for the symbol"),
|
|
@@ -8594,8 +8601,8 @@ var Schema22 = external_exports.object({
|
|
|
8594
8601
|
});
|
|
8595
8602
|
|
|
8596
8603
|
// ../../packages/core/src/tools/full-text-search.ts
|
|
8597
|
-
import
|
|
8598
|
-
import
|
|
8604
|
+
import fs21 from "fs";
|
|
8605
|
+
import path23 from "path";
|
|
8599
8606
|
var Schema23 = external_exports.object({
|
|
8600
8607
|
query: external_exports.string().min(1).describe("Search term \u2014 literal or /regex/"),
|
|
8601
8608
|
mode: external_exports.enum(["hybrid", "keyword", "semantic"]).optional().default("hybrid"),
|
|
@@ -8627,8 +8634,8 @@ var Schema25 = external_exports.object({
|
|
|
8627
8634
|
});
|
|
8628
8635
|
|
|
8629
8636
|
// ../../packages/core/src/tools/graph-snapshot.ts
|
|
8630
|
-
import
|
|
8631
|
-
import
|
|
8637
|
+
import fs22 from "fs";
|
|
8638
|
+
import path24 from "path";
|
|
8632
8639
|
var schema = external_exports.object({
|
|
8633
8640
|
name: external_exports.string().min(1).max(64).regex(/^[\w.-]+$/, "Name may only contain letters, digits, dots, underscores, hyphens").describe(
|
|
8634
8641
|
'Snapshot name (e.g. "before-refactor", "v1.0"). Used as the filename.'
|
|
@@ -8640,8 +8647,8 @@ var schema = external_exports.object({
|
|
|
8640
8647
|
});
|
|
8641
8648
|
|
|
8642
8649
|
// ../../packages/core/src/tools/graph-diff.ts
|
|
8643
|
-
import
|
|
8644
|
-
import
|
|
8650
|
+
import fs23 from "fs";
|
|
8651
|
+
import path25 from "path";
|
|
8645
8652
|
var schema2 = external_exports.object({
|
|
8646
8653
|
baseline: external_exports.string().min(1).describe('Name of the baseline snapshot (the "before" state).'),
|
|
8647
8654
|
current: external_exports.string().min(1).describe('Name of the current snapshot (the "after" state).'),
|
|
@@ -8682,8 +8689,8 @@ var Schema27 = external_exports.object({
|
|
|
8682
8689
|
});
|
|
8683
8690
|
|
|
8684
8691
|
// ../../packages/core/src/rules/loadConfig.ts
|
|
8685
|
-
import
|
|
8686
|
-
import
|
|
8692
|
+
import fs24 from "fs/promises";
|
|
8693
|
+
import path26 from "path";
|
|
8687
8694
|
|
|
8688
8695
|
// ../../node_modules/js-yaml/dist/js-yaml.mjs
|
|
8689
8696
|
function isNothing(subject) {
|
|
@@ -11312,24 +11319,24 @@ var Schema29 = external_exports.object({
|
|
|
11312
11319
|
});
|
|
11313
11320
|
|
|
11314
11321
|
// ../../packages/core/src/tools/ruleManager.ts
|
|
11315
|
-
import
|
|
11316
|
-
import path26 from "path";
|
|
11317
|
-
|
|
11318
|
-
// ../../packages/core/src/review/AuthorResolver.ts
|
|
11319
|
-
import fs25 from "fs/promises";
|
|
11322
|
+
import fs25 from "fs";
|
|
11320
11323
|
import path27 from "path";
|
|
11321
11324
|
|
|
11322
|
-
// ../../packages/core/src/review/
|
|
11325
|
+
// ../../packages/core/src/review/AuthorResolver.ts
|
|
11323
11326
|
import fs26 from "fs/promises";
|
|
11324
11327
|
import path28 from "path";
|
|
11325
11328
|
|
|
11326
|
-
// ../../packages/core/src/review/
|
|
11329
|
+
// ../../packages/core/src/review/CodeownersWriter.ts
|
|
11327
11330
|
import fs27 from "fs/promises";
|
|
11328
11331
|
import path29 from "path";
|
|
11329
11332
|
|
|
11330
|
-
// ../../packages/core/src/
|
|
11333
|
+
// ../../packages/core/src/review/loadConfig.ts
|
|
11334
|
+
import fs28 from "fs/promises";
|
|
11331
11335
|
import path30 from "path";
|
|
11332
|
-
|
|
11336
|
+
|
|
11337
|
+
// ../../packages/core/src/security/PathValidator.ts
|
|
11338
|
+
import path31 from "path";
|
|
11339
|
+
import fs29 from "fs";
|
|
11333
11340
|
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
11334
11341
|
|
|
11335
11342
|
// ../../packages/core/src/index.ts
|
|
@@ -11340,7 +11347,7 @@ init_logger();
|
|
|
11340
11347
|
|
|
11341
11348
|
// ../../packages/core/src/license/LicenseStore.ts
|
|
11342
11349
|
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, chmodSync, existsSync } from "fs";
|
|
11343
|
-
import
|
|
11350
|
+
import path32 from "path";
|
|
11344
11351
|
|
|
11345
11352
|
// ../../packages/core/src/license/types.ts
|
|
11346
11353
|
var FINGERPRINT_RE = /^sha256:[0-9a-f]{64}$/;
|
|
@@ -11363,19 +11370,19 @@ var API_BASE = process.env["CTXLOOM_API_BASE"] ?? "https://api.ctxloom.com";
|
|
|
11363
11370
|
|
|
11364
11371
|
// ../../packages/core/src/license/Fingerprint.ts
|
|
11365
11372
|
import crypto4 from "crypto";
|
|
11366
|
-
import
|
|
11373
|
+
import os3 from "os";
|
|
11367
11374
|
import { readFileSync as readFileSync2 } from "fs";
|
|
11368
11375
|
|
|
11369
11376
|
// ../../packages/core/src/license/index.ts
|
|
11370
|
-
import
|
|
11377
|
+
import os6 from "os";
|
|
11371
11378
|
|
|
11372
11379
|
// ../../packages/core/src/license/DistinctIdStore.ts
|
|
11373
11380
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
11374
|
-
import
|
|
11375
|
-
import
|
|
11381
|
+
import path33 from "path";
|
|
11382
|
+
import os4 from "os";
|
|
11376
11383
|
var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
11377
11384
|
function distinctIdPath(home) {
|
|
11378
|
-
return
|
|
11385
|
+
return path33.join(home ?? os4.homedir(), ".ctxloom", "distinct_id");
|
|
11379
11386
|
}
|
|
11380
11387
|
function isValidV4(id) {
|
|
11381
11388
|
return typeof id === "string" && UUID_V4_REGEX.test(id);
|
|
@@ -11394,9 +11401,9 @@ function getOrCreateDistinctId(home) {
|
|
|
11394
11401
|
}
|
|
11395
11402
|
const record = {
|
|
11396
11403
|
id: crypto.randomUUID(),
|
|
11397
|
-
alias_pending:
|
|
11404
|
+
alias_pending: os4.hostname()
|
|
11398
11405
|
};
|
|
11399
|
-
mkdirSync2(
|
|
11406
|
+
mkdirSync2(path33.dirname(filePath), { recursive: true });
|
|
11400
11407
|
writeFileSync2(filePath, JSON.stringify(record), { mode: 384 });
|
|
11401
11408
|
return record;
|
|
11402
11409
|
}
|
|
@@ -11422,7 +11429,7 @@ function resolveTelemetryLevel() {
|
|
|
11422
11429
|
}
|
|
11423
11430
|
var TELEMETRY_LEVEL = resolveTelemetryLevel();
|
|
11424
11431
|
var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
|
|
11425
|
-
var CTXLOOM_VERSION = "1.3.
|
|
11432
|
+
var CTXLOOM_VERSION = "1.3.1".length > 0 ? "1.3.1" : "dev";
|
|
11426
11433
|
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
11427
11434
|
var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
|
|
11428
11435
|
var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
|
|
@@ -11555,31 +11562,31 @@ function parseStack(stack) {
|
|
|
11555
11562
|
|
|
11556
11563
|
// ../../packages/core/src/license/FunnelMilestones.ts
|
|
11557
11564
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
11558
|
-
import
|
|
11559
|
-
import
|
|
11565
|
+
import path34 from "path";
|
|
11566
|
+
import os5 from "os";
|
|
11560
11567
|
|
|
11561
11568
|
// ../../packages/core/src/license/TelemetryNotice.ts
|
|
11562
11569
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
11563
|
-
import
|
|
11564
|
-
import
|
|
11570
|
+
import path35 from "path";
|
|
11571
|
+
import os7 from "os";
|
|
11565
11572
|
|
|
11566
11573
|
// ../../packages/core/src/server/ProjectState.ts
|
|
11567
|
-
import
|
|
11574
|
+
import path37 from "path";
|
|
11568
11575
|
|
|
11569
11576
|
// ../../packages/core/src/server/projectId.ts
|
|
11570
11577
|
import crypto5 from "crypto";
|
|
11571
|
-
import
|
|
11578
|
+
import path36 from "path";
|
|
11572
11579
|
|
|
11573
11580
|
// ../../packages/core/src/server/ProjectStateManager.ts
|
|
11574
11581
|
init_logger();
|
|
11575
11582
|
|
|
11576
11583
|
// ../../packages/core/src/server/resolveProjectRoot.ts
|
|
11577
|
-
import
|
|
11578
|
-
import
|
|
11584
|
+
import fs30 from "fs";
|
|
11585
|
+
import path38 from "path";
|
|
11579
11586
|
|
|
11580
11587
|
// server/loader.ts
|
|
11581
11588
|
async function loadContext(root) {
|
|
11582
|
-
const absRoot =
|
|
11589
|
+
const absRoot = path39.resolve(root);
|
|
11583
11590
|
const overlay = new GitOverlayStore(absRoot);
|
|
11584
11591
|
const gitEnabled = await overlay.loadSnapshot();
|
|
11585
11592
|
const graph = new DependencyGraph();
|
|
@@ -11832,21 +11839,21 @@ function buildOwnershipRouter(ctx) {
|
|
|
11832
11839
|
|
|
11833
11840
|
// server/routes/file.ts
|
|
11834
11841
|
import { Router as Router7 } from "express";
|
|
11835
|
-
import
|
|
11836
|
-
import
|
|
11842
|
+
import fs31 from "fs/promises";
|
|
11843
|
+
import path40 from "path";
|
|
11837
11844
|
function buildFileRouter(ctx) {
|
|
11838
11845
|
const router = Router7();
|
|
11839
11846
|
router.get("/", async (req, res) => {
|
|
11840
11847
|
const rel = req.query.path;
|
|
11841
11848
|
if (!rel) return res.status(400).json({ error: "missing path" });
|
|
11842
|
-
const abs =
|
|
11843
|
-
const rootBoundary = ctx.root.endsWith(
|
|
11849
|
+
const abs = path40.resolve(ctx.root, rel);
|
|
11850
|
+
const rootBoundary = ctx.root.endsWith(path40.sep) ? ctx.root : ctx.root + path40.sep;
|
|
11844
11851
|
if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
|
|
11845
11852
|
return res.status(403).json({ error: "forbidden" });
|
|
11846
11853
|
}
|
|
11847
11854
|
try {
|
|
11848
|
-
const content = await
|
|
11849
|
-
const ext =
|
|
11855
|
+
const content = await fs31.readFile(abs, "utf-8");
|
|
11856
|
+
const ext = path40.extname(abs).slice(1);
|
|
11850
11857
|
res.json({ content, lines: content.split("\n").length, ext });
|
|
11851
11858
|
} catch {
|
|
11852
11859
|
res.status(404).json({ error: "not found" });
|
|
@@ -11858,7 +11865,7 @@ function buildFileRouter(ctx) {
|
|
|
11858
11865
|
// server/routes/open.ts
|
|
11859
11866
|
import { Router as Router8 } from "express";
|
|
11860
11867
|
import { execFile as execFile2 } from "child_process";
|
|
11861
|
-
import
|
|
11868
|
+
import path41 from "path";
|
|
11862
11869
|
function tryOpen(bin, abs) {
|
|
11863
11870
|
return new Promise((resolve) => {
|
|
11864
11871
|
execFile2(bin, [abs], { timeout: 5e3 }, (err) => resolve(!err));
|
|
@@ -11869,8 +11876,8 @@ function buildOpenRouter(ctx) {
|
|
|
11869
11876
|
router.post("/", async (req, res) => {
|
|
11870
11877
|
const rel = req.body?.path;
|
|
11871
11878
|
if (!rel || typeof rel !== "string") return res.status(400).json({ error: "missing path" });
|
|
11872
|
-
const abs =
|
|
11873
|
-
const rootBoundary = ctx.root.endsWith(
|
|
11879
|
+
const abs = path41.resolve(ctx.root, rel);
|
|
11880
|
+
const rootBoundary = ctx.root.endsWith(path41.sep) ? ctx.root : ctx.root + path41.sep;
|
|
11874
11881
|
if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
|
|
11875
11882
|
return res.status(403).json({ error: "forbidden" });
|
|
11876
11883
|
}
|
|
@@ -11882,8 +11889,8 @@ function buildOpenRouter(ctx) {
|
|
|
11882
11889
|
|
|
11883
11890
|
// server/routes/tokens.ts
|
|
11884
11891
|
import { Router as Router9 } from "express";
|
|
11885
|
-
import
|
|
11886
|
-
import
|
|
11892
|
+
import path42 from "path";
|
|
11893
|
+
import fs32 from "fs";
|
|
11887
11894
|
var CHARS_PER_TOKEN = 4;
|
|
11888
11895
|
var cache = null;
|
|
11889
11896
|
function buildTokensRouter(ctx) {
|
|
@@ -11898,9 +11905,9 @@ function buildTokensRouter(ctx) {
|
|
|
11898
11905
|
let fullChars = 0;
|
|
11899
11906
|
let skeletonChars = 0;
|
|
11900
11907
|
for (const file of files) {
|
|
11901
|
-
const absPath =
|
|
11908
|
+
const absPath = path42.join(ctx.root, file);
|
|
11902
11909
|
try {
|
|
11903
|
-
const content =
|
|
11910
|
+
const content = fs32.readFileSync(absPath, "utf-8");
|
|
11904
11911
|
fullChars += content.length;
|
|
11905
11912
|
const skeleton = await skeletonizer.skeletonize(absPath);
|
|
11906
11913
|
skeletonChars += skeleton.length;
|
|
@@ -12001,17 +12008,17 @@ function buildFileTrendsRouter(ctx) {
|
|
|
12001
12008
|
|
|
12002
12009
|
// server/routes/projects.ts
|
|
12003
12010
|
import { Router as Router12 } from "express";
|
|
12004
|
-
import
|
|
12011
|
+
import path44 from "path";
|
|
12005
12012
|
|
|
12006
12013
|
// server/projects.ts
|
|
12007
12014
|
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
12008
|
-
import
|
|
12009
|
-
import
|
|
12015
|
+
import os8 from "os";
|
|
12016
|
+
import path43 from "path";
|
|
12010
12017
|
import crypto6 from "crypto";
|
|
12011
|
-
var HOME =
|
|
12012
|
-
var REGISTRY_PATH =
|
|
12018
|
+
var HOME = os8.homedir();
|
|
12019
|
+
var REGISTRY_PATH = path43.join(HOME, ".ctxloom", "repos.json");
|
|
12013
12020
|
function slugFor(root) {
|
|
12014
|
-
const abs =
|
|
12021
|
+
const abs = path43.resolve(root);
|
|
12015
12022
|
return crypto6.createHash("sha1").update(abs).digest("hex").slice(0, 12);
|
|
12016
12023
|
}
|
|
12017
12024
|
function readRegistry() {
|
|
@@ -12026,27 +12033,27 @@ function readRegistry() {
|
|
|
12026
12033
|
}
|
|
12027
12034
|
}
|
|
12028
12035
|
function listProjects(defaultRoot) {
|
|
12029
|
-
const absDefault =
|
|
12036
|
+
const absDefault = path43.resolve(defaultRoot);
|
|
12030
12037
|
const out = [
|
|
12031
12038
|
{
|
|
12032
12039
|
slug: slugFor(absDefault),
|
|
12033
|
-
name:
|
|
12040
|
+
name: path43.basename(absDefault) || absDefault,
|
|
12034
12041
|
root: absDefault,
|
|
12035
12042
|
isDefault: true,
|
|
12036
|
-
hasSnapshot: existsSync5(
|
|
12043
|
+
hasSnapshot: existsSync5(path43.join(absDefault, ".ctxloom"))
|
|
12037
12044
|
}
|
|
12038
12045
|
];
|
|
12039
12046
|
const seen = /* @__PURE__ */ new Set([absDefault]);
|
|
12040
12047
|
for (const entry of readRegistry()) {
|
|
12041
|
-
const abs =
|
|
12048
|
+
const abs = path43.resolve(entry.root);
|
|
12042
12049
|
if (seen.has(abs)) continue;
|
|
12043
12050
|
seen.add(abs);
|
|
12044
12051
|
const item = {
|
|
12045
12052
|
slug: slugFor(abs),
|
|
12046
|
-
name: entry.name ?? (
|
|
12053
|
+
name: entry.name ?? (path43.basename(abs) || abs),
|
|
12047
12054
|
root: abs,
|
|
12048
12055
|
isDefault: false,
|
|
12049
|
-
hasSnapshot: existsSync5(
|
|
12056
|
+
hasSnapshot: existsSync5(path43.join(abs, ".ctxloom"))
|
|
12050
12057
|
};
|
|
12051
12058
|
if (entry.alias !== void 0) item.alias = entry.alias;
|
|
12052
12059
|
out.push(item);
|
|
@@ -12099,7 +12106,7 @@ function buildProjectsRouter(deps) {
|
|
|
12099
12106
|
} catch (err) {
|
|
12100
12107
|
const detail = err instanceof Error ? err.message : String(err);
|
|
12101
12108
|
res.status(500).json({
|
|
12102
|
-
error: `failed to switch to ${
|
|
12109
|
+
error: `failed to switch to ${path44.basename(target.root)}: ${detail}`
|
|
12103
12110
|
});
|
|
12104
12111
|
}
|
|
12105
12112
|
});
|
|
@@ -12156,7 +12163,7 @@ function buildTelemetryRouter() {
|
|
|
12156
12163
|
}
|
|
12157
12164
|
|
|
12158
12165
|
// server/index.ts
|
|
12159
|
-
var __dirname2 =
|
|
12166
|
+
var __dirname2 = path45.dirname(fileURLToPath2(import.meta.url));
|
|
12160
12167
|
async function startDashboard(options) {
|
|
12161
12168
|
const { root, port, open } = options;
|
|
12162
12169
|
console.log(`ctxloom dashboard \u2014 loading context from ${root}...`);
|
|
@@ -12215,9 +12222,9 @@ async function startDashboard(options) {
|
|
|
12215
12222
|
}
|
|
12216
12223
|
activeWatcher = null;
|
|
12217
12224
|
}
|
|
12218
|
-
const snapshotDir =
|
|
12225
|
+
const snapshotDir = path45.join(targetRoot, ".ctxloom");
|
|
12219
12226
|
try {
|
|
12220
|
-
activeWatcher =
|
|
12227
|
+
activeWatcher = fs33.watch(snapshotDir, (_event, filename) => {
|
|
12221
12228
|
if (!filename || !filename.includes("snapshot")) return;
|
|
12222
12229
|
if (debounce) clearTimeout(debounce);
|
|
12223
12230
|
debounce = setTimeout(async () => {
|
|
@@ -12246,12 +12253,12 @@ async function startDashboard(options) {
|
|
|
12246
12253
|
attachSnapshotWatcher(newRoot);
|
|
12247
12254
|
}
|
|
12248
12255
|
}));
|
|
12249
|
-
const clientDist =
|
|
12250
|
-
const clientDistExists =
|
|
12256
|
+
const clientDist = path45.join(__dirname2, "../dashboard/client");
|
|
12257
|
+
const clientDistExists = fs33.existsSync(path45.join(clientDist, "index.html"));
|
|
12251
12258
|
if (clientDistExists) {
|
|
12252
12259
|
app.use(express.static(clientDist, { dotfiles: "allow" }));
|
|
12253
12260
|
app.get(/.*/, (_req, res) => {
|
|
12254
|
-
res.sendFile(
|
|
12261
|
+
res.sendFile(path45.join(clientDist, "index.html"), { dotfiles: "allow" });
|
|
12255
12262
|
});
|
|
12256
12263
|
} else {
|
|
12257
12264
|
app.get(/^\/(?!api\/).*/, (_req, res) => {
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// packages/core/src/utils/stats.ts
|
|
2
|
+
function percentile(values, p) {
|
|
3
|
+
if (values.length === 0) return null;
|
|
4
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
5
|
+
const idx = Math.floor((sorted.length - 1) * p);
|
|
6
|
+
return sorted[idx];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// packages/core/src/budget/budgetStats.ts
|
|
10
|
+
function summarize(events, windowStart, windowEnd) {
|
|
11
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const e of events) {
|
|
13
|
+
const existing = byTool.get(e.tool);
|
|
14
|
+
if (existing) existing.push(e);
|
|
15
|
+
else byTool.set(e.tool, [e]);
|
|
16
|
+
}
|
|
17
|
+
const fallbackTable = [];
|
|
18
|
+
const distributionTable = [];
|
|
19
|
+
for (const [tool, bucket] of byTool) {
|
|
20
|
+
const fallbackUsed = bucket.filter((e) => e.event === "mcp.fallback.used");
|
|
21
|
+
if (fallbackUsed.length > 0) {
|
|
22
|
+
let skeleton = 0, truncate = 0, error = 0;
|
|
23
|
+
for (const e of fallbackUsed) {
|
|
24
|
+
const mode = typeof e.mode === "string" ? e.mode : "";
|
|
25
|
+
if (mode === "skeleton" || mode === "skeleton+truncate") skeleton++;
|
|
26
|
+
else if (mode === "truncate" || mode === "truncate-fallback") truncate++;
|
|
27
|
+
else if (mode === "error") error++;
|
|
28
|
+
}
|
|
29
|
+
const total = skeleton + truncate + error;
|
|
30
|
+
if (total > 0) {
|
|
31
|
+
fallbackTable.push({
|
|
32
|
+
tool,
|
|
33
|
+
breaches: fallbackUsed.length,
|
|
34
|
+
skeletonPct: Math.round(skeleton / total * 100),
|
|
35
|
+
truncatePct: Math.round(truncate / total * 100),
|
|
36
|
+
errorPct: Math.round(error / total * 100)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
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);
|
|
41
|
+
if (tokens.length > 0) {
|
|
42
|
+
distributionTable.push({
|
|
43
|
+
tool,
|
|
44
|
+
n: tokens.length,
|
|
45
|
+
min: Math.min(...tokens),
|
|
46
|
+
p50: percentile(tokens, 0.5),
|
|
47
|
+
p75: percentile(tokens, 0.75),
|
|
48
|
+
p95: percentile(tokens, 0.95),
|
|
49
|
+
max: Math.max(...tokens)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
fallbackTable.sort((a, b) => a.tool.localeCompare(b.tool));
|
|
54
|
+
distributionTable.sort((a, b) => a.tool.localeCompare(b.tool));
|
|
55
|
+
return {
|
|
56
|
+
windowStart: windowStart.toISOString(),
|
|
57
|
+
windowEnd: windowEnd.toISOString(),
|
|
58
|
+
totalEvents: events.length,
|
|
59
|
+
fallbackTable,
|
|
60
|
+
distributionTable
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function renderSummary(s) {
|
|
64
|
+
const lines = [];
|
|
65
|
+
const startDate = s.windowStart.slice(0, 10);
|
|
66
|
+
const endDate = s.windowEnd.slice(0, 10);
|
|
67
|
+
lines.push(`Budget event summary (${startDate} \u2192 ${endDate}, ${s.totalEvents} events)`);
|
|
68
|
+
lines.push("");
|
|
69
|
+
if (s.totalEvents === 0) {
|
|
70
|
+
lines.push("No events in window. Either:");
|
|
71
|
+
lines.push(" - No budget breaches occurred (everything fit under budgets)");
|
|
72
|
+
lines.push(` - CTXLOOM_TELEMETRY_LEVEL is not set to "full" in the MCP server's env`);
|
|
73
|
+
lines.push(" - No tool calls have opted into the budget surface yet");
|
|
74
|
+
return lines.join("\n");
|
|
75
|
+
}
|
|
76
|
+
lines.push("Fallback distribution per tool");
|
|
77
|
+
lines.push("");
|
|
78
|
+
if (s.fallbackTable.length === 0) {
|
|
79
|
+
lines.push(" (no fallback events recorded in window)");
|
|
80
|
+
} else {
|
|
81
|
+
lines.push("| Tool | Breaches | Skeleton % | Truncate % | Error % |");
|
|
82
|
+
lines.push("|----------------------------|---------:|-----------:|-----------:|--------:|");
|
|
83
|
+
for (const r of s.fallbackTable) {
|
|
84
|
+
lines.push(
|
|
85
|
+
`| ${r.tool.padEnd(26)} | ${String(r.breaches).padStart(8)} | ${String(r.skeletonPct).padStart(9)}% | ${String(r.truncatePct).padStart(9)}% | ${String(r.errorPct).padStart(6)}% |`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
lines.push("");
|
|
90
|
+
lines.push("Original-token distribution per tool (over-budget calls only)");
|
|
91
|
+
lines.push("");
|
|
92
|
+
if (s.distributionTable.length === 0) {
|
|
93
|
+
lines.push(" (no budget-breach events recorded in window)");
|
|
94
|
+
} else {
|
|
95
|
+
lines.push("| Tool | n | min | p50 | p75 | p95 | max |");
|
|
96
|
+
lines.push("|----------------------------|----:|-------:|-------:|-------:|-------:|-------:|");
|
|
97
|
+
for (const r of s.distributionTable) {
|
|
98
|
+
lines.push(
|
|
99
|
+
`| ${r.tool.padEnd(26)} | ${String(r.n).padStart(3)} | ${fmt(r.min)} | ${fmt(r.p50)} | ${fmt(r.p75)} | ${fmt(r.p95)} | ${fmt(r.max)} |`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
lines.push("");
|
|
103
|
+
lines.push("The **p75** column is the input for per-tool default budget tuning");
|
|
104
|
+
lines.push("(packages/core/src/tools/*.ts \u2192 DEFAULT_MAX_RESPONSE_TOKENS).");
|
|
105
|
+
}
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
function fmt(n) {
|
|
109
|
+
return n === null ? " \u2014" : String(n).padStart(6);
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
percentile,
|
|
113
|
+
renderSummary,
|
|
114
|
+
summarize
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=budgetStats-TURA232F.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-TYDMSHV7.js";
|
|
4
|
+
|
|
5
|
+
// packages/core/src/budget/eventCollector.ts
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import path from "path";
|
|
9
|
+
var DEFAULT_TELEMETRY_DIR = path.join(os.homedir(), ".ctxloom", "telemetry");
|
|
10
|
+
function telemetryDir() {
|
|
11
|
+
const raw = process.env.CTXLOOM_TELEMETRY_DIR ?? DEFAULT_TELEMETRY_DIR;
|
|
12
|
+
if (raw.includes("..") || !path.isAbsolute(raw)) {
|
|
13
|
+
if (!telemetryDirWarned) {
|
|
14
|
+
telemetryDirWarned = true;
|
|
15
|
+
logger.warn('CTXLOOM_TELEMETRY_DIR rejected \u2014 must be an absolute path with no ".." segments; using default', {
|
|
16
|
+
rejected: raw,
|
|
17
|
+
fallback: DEFAULT_TELEMETRY_DIR
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return DEFAULT_TELEMETRY_DIR;
|
|
21
|
+
}
|
|
22
|
+
return path.resolve(raw);
|
|
23
|
+
}
|
|
24
|
+
var telemetryDirWarned = false;
|
|
25
|
+
function filenameForDate(date) {
|
|
26
|
+
const y = date.getUTCFullYear();
|
|
27
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
28
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
29
|
+
return `budget-events-${y}-${m}-${d}.jsonl`;
|
|
30
|
+
}
|
|
31
|
+
function appendEvent(event, now = /* @__PURE__ */ new Date()) {
|
|
32
|
+
try {
|
|
33
|
+
const dir = telemetryDir();
|
|
34
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
35
|
+
const file = path.join(dir, filenameForDate(now));
|
|
36
|
+
const persisted = { ts: now.toISOString(), ...event };
|
|
37
|
+
fs.appendFileSync(file, JSON.stringify(persisted) + "\n", "utf-8");
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (!appendFailureWarned) {
|
|
40
|
+
appendFailureWarned = true;
|
|
41
|
+
logger.warn("telemetry sink append failed (further failures suppressed)", {
|
|
42
|
+
error: err instanceof Error ? err.message : String(err)
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
var appendFailureWarned = false;
|
|
48
|
+
function __resetTelemetryWarnFlagsForTests() {
|
|
49
|
+
telemetryDirWarned = false;
|
|
50
|
+
appendFailureWarned = false;
|
|
51
|
+
}
|
|
52
|
+
function readEvents(opts = {}) {
|
|
53
|
+
const until = opts.until ?? /* @__PURE__ */ new Date();
|
|
54
|
+
const since = opts.since ?? new Date(until.getTime() - 14 * 24 * 60 * 60 * 1e3);
|
|
55
|
+
const dir = telemetryDir();
|
|
56
|
+
if (!fs.existsSync(dir)) return [];
|
|
57
|
+
const out = [];
|
|
58
|
+
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)) {
|
|
59
|
+
const file = path.join(dir, filenameForDate(cursor));
|
|
60
|
+
if (!fs.existsSync(file)) continue;
|
|
61
|
+
const text = fs.readFileSync(file, "utf-8");
|
|
62
|
+
for (const line of text.split("\n")) {
|
|
63
|
+
if (line.trim() === "") continue;
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(line);
|
|
67
|
+
} catch {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!isPersistedEvent(parsed)) continue;
|
|
71
|
+
const eventTs = new Date(parsed.ts).getTime();
|
|
72
|
+
if (!Number.isFinite(eventTs)) continue;
|
|
73
|
+
if (eventTs < since.getTime() || eventTs > until.getTime()) continue;
|
|
74
|
+
if (opts.tool && parsed.tool !== opts.tool) continue;
|
|
75
|
+
out.push(parsed);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
function isPersistedEvent(v) {
|
|
81
|
+
if (!v || typeof v !== "object") return false;
|
|
82
|
+
const o = v;
|
|
83
|
+
return typeof o.ts === "string" && typeof o.event === "string" && typeof o.tool === "string";
|
|
84
|
+
}
|
|
85
|
+
var diskSink = {
|
|
86
|
+
append(event) {
|
|
87
|
+
appendEvent(event);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
telemetryDir,
|
|
93
|
+
filenameForDate,
|
|
94
|
+
appendEvent,
|
|
95
|
+
__resetTelemetryWarnFlagsForTests,
|
|
96
|
+
readEvents,
|
|
97
|
+
diskSink
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=chunk-5I6CJITG.js.map
|
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
collectFiles,
|
|
6
6
|
generateEmbedding
|
|
7
7
|
} from "./chunk-UVR65QBJ.js";
|
|
8
|
+
import {
|
|
9
|
+
diskSink
|
|
10
|
+
} from "./chunk-5I6CJITG.js";
|
|
8
11
|
import {
|
|
9
12
|
logger
|
|
10
13
|
} from "./chunk-TYDMSHV7.js";
|
|
@@ -4634,13 +4637,18 @@ function readBudgetArgs(args) {
|
|
|
4634
4637
|
function isBudgetDisabled() {
|
|
4635
4638
|
return process.env.CTXLOOM_DISABLE_BUDGET === "1";
|
|
4636
4639
|
}
|
|
4637
|
-
function emitTelemetry(event) {
|
|
4640
|
+
function emitTelemetry(event, sink = diskSink) {
|
|
4638
4641
|
if (process.env.CTXLOOM_TELEMETRY_LEVEL !== "full") return;
|
|
4639
4642
|
logger.info(event.event, event);
|
|
4643
|
+
try {
|
|
4644
|
+
sink.append(event);
|
|
4645
|
+
} catch {
|
|
4646
|
+
}
|
|
4640
4647
|
}
|
|
4641
4648
|
async function enforceBudget(opts) {
|
|
4642
4649
|
const { full, args, toolName, defaultMaxTokens, skeletonProducer } = opts;
|
|
4643
4650
|
const estimate = opts.estimator ?? defaultTokenEstimator;
|
|
4651
|
+
const sink = opts.sink ?? opts.ctx?.telemetrySink ?? diskSink;
|
|
4644
4652
|
const originalTokens = estimate(full);
|
|
4645
4653
|
if (isBudgetDisabled()) {
|
|
4646
4654
|
return {
|
|
@@ -4706,7 +4714,7 @@ async function enforceBudget(opts) {
|
|
|
4706
4714
|
original_tokens: originalTokens,
|
|
4707
4715
|
budget,
|
|
4708
4716
|
ratio: originalTokens / budget
|
|
4709
|
-
});
|
|
4717
|
+
}, sink);
|
|
4710
4718
|
const mode = args.on_budget_exceeded ?? "skeleton";
|
|
4711
4719
|
if (mode === "error") {
|
|
4712
4720
|
const err = new Error(
|
|
@@ -4725,7 +4733,7 @@ async function enforceBudget(opts) {
|
|
|
4725
4733
|
tool: toolName,
|
|
4726
4734
|
fallback_reason: "budget_exceeded",
|
|
4727
4735
|
mode: "truncate"
|
|
4728
|
-
});
|
|
4736
|
+
}, sink);
|
|
4729
4737
|
return {
|
|
4730
4738
|
text: sliced2,
|
|
4731
4739
|
meta: {
|
|
@@ -4745,7 +4753,7 @@ async function enforceBudget(opts) {
|
|
|
4745
4753
|
tool: toolName,
|
|
4746
4754
|
fallback_reason: "budget_exceeded",
|
|
4747
4755
|
mode: "skeleton"
|
|
4748
|
-
});
|
|
4756
|
+
}, sink);
|
|
4749
4757
|
return {
|
|
4750
4758
|
text: skeleton,
|
|
4751
4759
|
meta: {
|
|
@@ -4762,7 +4770,7 @@ async function enforceBudget(opts) {
|
|
|
4762
4770
|
tool: toolName,
|
|
4763
4771
|
fallback_reason: "budget_exceeded",
|
|
4764
4772
|
mode: "skeleton+truncate"
|
|
4765
|
-
});
|
|
4773
|
+
}, sink);
|
|
4766
4774
|
return {
|
|
4767
4775
|
text: slicedSk,
|
|
4768
4776
|
meta: {
|
|
@@ -4779,7 +4787,7 @@ async function enforceBudget(opts) {
|
|
|
4779
4787
|
tool: toolName,
|
|
4780
4788
|
fallback_reason: "skeleton_failed",
|
|
4781
4789
|
mode: "truncate-fallback"
|
|
4782
|
-
});
|
|
4790
|
+
}, sink);
|
|
4783
4791
|
return {
|
|
4784
4792
|
text: sliced,
|
|
4785
4793
|
meta: {
|
|
@@ -4887,6 +4895,7 @@ function registerSearchTool(registry, ctx) {
|
|
|
4887
4895
|
if (!hasBudgetArgs(args)) return full;
|
|
4888
4896
|
const skeletonProducer = async () => renderResults(parsed.query, ranked, false);
|
|
4889
4897
|
const result = await enforceBudget({
|
|
4898
|
+
ctx,
|
|
4890
4899
|
full,
|
|
4891
4900
|
args: readBudgetArgs(args),
|
|
4892
4901
|
toolName: "ctx_search",
|
|
@@ -4946,6 +4955,7 @@ function registerFileTool(registry, ctx) {
|
|
|
4946
4955
|
const absPath = validator.validate(parsed.path);
|
|
4947
4956
|
const skeletonizer = await ctx.getSkeletonizer(parsed.project_root);
|
|
4948
4957
|
const result = await enforceBudget({
|
|
4958
|
+
ctx,
|
|
4949
4959
|
full,
|
|
4950
4960
|
args: readBudgetArgs(args),
|
|
4951
4961
|
toolName: "ctx_get_file",
|
|
@@ -5054,6 +5064,7 @@ ${sk}`;
|
|
|
5054
5064
|
return renderPacket({ ...parts, primaryContent: primarySkeleton });
|
|
5055
5065
|
};
|
|
5056
5066
|
const result = await enforceBudget({
|
|
5067
|
+
ctx,
|
|
5057
5068
|
full,
|
|
5058
5069
|
args: readBudgetArgs(args),
|
|
5059
5070
|
toolName: "ctx_get_context_packet",
|
|
@@ -5217,6 +5228,7 @@ function registerDefinitionTool(registry, ctx) {
|
|
|
5217
5228
|
}
|
|
5218
5229
|
if (!hasBudgetArgs(args)) return full;
|
|
5219
5230
|
const result = await enforceBudget({
|
|
5231
|
+
ctx,
|
|
5220
5232
|
full,
|
|
5221
5233
|
args: readBudgetArgs(args),
|
|
5222
5234
|
toolName: "ctx_get_definition",
|
|
@@ -6258,6 +6270,7 @@ function registerWikiGenerateTool(registry, ctx) {
|
|
|
6258
6270
|
const full = detail_level === "minimal" ? renderMinimal() : renderStandard();
|
|
6259
6271
|
if (!hasBudgetArgs(args)) return full;
|
|
6260
6272
|
const budgetResult = await enforceBudget({
|
|
6273
|
+
ctx,
|
|
6261
6274
|
full,
|
|
6262
6275
|
args: readBudgetArgs(args),
|
|
6263
6276
|
toolName: "ctx_wiki_generate",
|
|
@@ -6405,6 +6418,7 @@ function registerGitDiffReviewTool(registry, ctx) {
|
|
|
6405
6418
|
const maybeBudget = async (full2, skeletonProducer) => {
|
|
6406
6419
|
if (!hasBudgetArgs(args)) return full2;
|
|
6407
6420
|
const result = await enforceBudget({
|
|
6421
|
+
ctx,
|
|
6408
6422
|
full: full2,
|
|
6409
6423
|
args: readBudgetArgs(args),
|
|
6410
6424
|
toolName: "ctx_git_diff_review",
|
|
@@ -6622,6 +6636,7 @@ function registerRefactorPreviewTool(registry, ctx) {
|
|
|
6622
6636
|
const full = render(true);
|
|
6623
6637
|
if (!hasBudgetArgs(args)) return full;
|
|
6624
6638
|
const result = await enforceBudget({
|
|
6639
|
+
ctx,
|
|
6625
6640
|
full,
|
|
6626
6641
|
args: readBudgetArgs(args),
|
|
6627
6642
|
toolName: "ctx_refactor_preview",
|
|
@@ -6743,6 +6758,7 @@ function registerExecutionFlowTool(registry, ctx) {
|
|
|
6743
6758
|
const maybeBudget = async (full) => {
|
|
6744
6759
|
if (!hasBudgetArgs(args)) return full;
|
|
6745
6760
|
const result = await enforceBudget({
|
|
6761
|
+
ctx,
|
|
6746
6762
|
full,
|
|
6747
6763
|
args: readBudgetArgs(args),
|
|
6748
6764
|
toolName: "ctx_execution_flow",
|
|
@@ -6899,7 +6915,7 @@ var Schema20 = z22.object({
|
|
|
6899
6915
|
function escapeXML19(text) {
|
|
6900
6916
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
6901
6917
|
}
|
|
6902
|
-
function registerCrossRepoSearchTool(registry,
|
|
6918
|
+
function registerCrossRepoSearchTool(registry, ctx, registryFilePath) {
|
|
6903
6919
|
const repoRegistryPath = registryFilePath ?? path18.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".ctxloom", "repos.json");
|
|
6904
6920
|
registry.register(
|
|
6905
6921
|
"ctx_cross_repo_search",
|
|
@@ -7007,6 +7023,7 @@ function registerCrossRepoSearchTool(registry, _ctx, registryFilePath) {
|
|
|
7007
7023
|
const full = render(true);
|
|
7008
7024
|
if (!hasBudgetArgs(args)) return full;
|
|
7009
7025
|
const result = await enforceBudget({
|
|
7026
|
+
ctx,
|
|
7010
7027
|
full,
|
|
7011
7028
|
args: readBudgetArgs(args),
|
|
7012
7029
|
toolName: "ctx_cross_repo_search",
|
|
@@ -7116,6 +7133,7 @@ function registerApplyRefactorTool(registry, ctx) {
|
|
|
7116
7133
|
const full = xml.join("\n");
|
|
7117
7134
|
if (!hasBudgetArgs(args)) return full;
|
|
7118
7135
|
const result = await enforceBudget({
|
|
7136
|
+
ctx,
|
|
7119
7137
|
full,
|
|
7120
7138
|
args: readBudgetArgs(args),
|
|
7121
7139
|
toolName: "ctx_apply_refactor",
|
|
@@ -7333,6 +7351,7 @@ function registerFullTextSearchTool(registry, ctx) {
|
|
|
7333
7351
|
const maybeBudget = async (full, skeletonProducer) => {
|
|
7334
7352
|
if (!hasBudgetArgs(args)) return full;
|
|
7335
7353
|
const result = await enforceBudget({
|
|
7354
|
+
ctx,
|
|
7336
7355
|
full,
|
|
7337
7356
|
args: readBudgetArgs(args),
|
|
7338
7357
|
toolName: "ctx_full_text_search",
|
|
@@ -7897,6 +7916,7 @@ function registerFindLargeFunctionsTool(registry, ctx) {
|
|
|
7897
7916
|
}
|
|
7898
7917
|
if (!hasBudgetArgs(args)) return full;
|
|
7899
7918
|
const result = await enforceBudget({
|
|
7919
|
+
ctx,
|
|
7900
7920
|
full,
|
|
7901
7921
|
args: readBudgetArgs(args),
|
|
7902
7922
|
toolName: "ctx_find_large_functions",
|
|
@@ -9342,7 +9362,7 @@ var TELEMETRY_DISABLED = TELEMETRY_LEVEL === "off";
|
|
|
9342
9362
|
function getTelemetryLevel() {
|
|
9343
9363
|
return TELEMETRY_LEVEL;
|
|
9344
9364
|
}
|
|
9345
|
-
var CTXLOOM_VERSION = "1.3.
|
|
9365
|
+
var CTXLOOM_VERSION = "1.3.1".length > 0 ? "1.3.1" : "dev";
|
|
9346
9366
|
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
9347
9367
|
var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (true ? "phc_CiDkmFLcZ2K6uCpcoSUQLmFrnnUvsyXGhSxopX5TVKE6" : "");
|
|
9348
9368
|
var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (true ? "https://81c94a0f04a8e242dee493ac1e17f733@o4508531702497280.ingest.de.sentry.io/4511256875368528" : "");
|
|
@@ -10072,4 +10092,4 @@ export {
|
|
|
10072
10092
|
FirstTouchTracker,
|
|
10073
10093
|
EmittedOnceTracker
|
|
10074
10094
|
};
|
|
10075
|
-
//# sourceMappingURL=chunk-
|
|
10095
|
+
//# sourceMappingURL=chunk-TIYTPWYN.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__resetTelemetryWarnFlagsForTests,
|
|
3
|
+
appendEvent,
|
|
4
|
+
diskSink,
|
|
5
|
+
filenameForDate,
|
|
6
|
+
readEvents,
|
|
7
|
+
telemetryDir
|
|
8
|
+
} from "./chunk-5I6CJITG.js";
|
|
9
|
+
import "./chunk-TYDMSHV7.js";
|
|
10
|
+
export {
|
|
11
|
+
__resetTelemetryWarnFlagsForTests,
|
|
12
|
+
appendEvent,
|
|
13
|
+
diskSink,
|
|
14
|
+
filenameForDate,
|
|
15
|
+
readEvents,
|
|
16
|
+
telemetryDir
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=eventCollector-QSRBVUDF.js.map
|
package/dist/index.js
CHANGED
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
validateDefaultRoot,
|
|
50
50
|
wrapWithIndexingEnvelope,
|
|
51
51
|
writeCODEOWNERS
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-TIYTPWYN.js";
|
|
53
53
|
import {
|
|
54
54
|
VectorStore
|
|
55
55
|
} from "./chunk-DVI2RWJR.js";
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
generateEmbedding,
|
|
58
58
|
indexDirectory
|
|
59
59
|
} from "./chunk-UVR65QBJ.js";
|
|
60
|
+
import "./chunk-5I6CJITG.js";
|
|
60
61
|
import {
|
|
61
62
|
logger
|
|
62
63
|
} from "./chunk-TYDMSHV7.js";
|
|
@@ -1018,7 +1019,7 @@ try {
|
|
|
1018
1019
|
} catch {
|
|
1019
1020
|
}
|
|
1020
1021
|
var args = process.argv.slice(2);
|
|
1021
|
-
var ctxloomVersion = "1.3.
|
|
1022
|
+
var ctxloomVersion = "1.3.1".length > 0 ? "1.3.1" : "dev";
|
|
1022
1023
|
if (args.includes("--version") || args.includes("-v")) {
|
|
1023
1024
|
process.stdout.write(`ctxloom ${ctxloomVersion}
|
|
1024
1025
|
`);
|
|
@@ -1086,12 +1087,12 @@ function buildActivityFromOverlay(store) {
|
|
|
1086
1087
|
lastCommitTimestamp
|
|
1087
1088
|
}));
|
|
1088
1089
|
}
|
|
1089
|
-
var LICENSE_GATE_BYPASS_COMMANDS = /* @__PURE__ */ new Set(["trial", "activate", "deactivate", "status", "--help"]);
|
|
1090
|
+
var LICENSE_GATE_BYPASS_COMMANDS = /* @__PURE__ */ new Set(["trial", "activate", "deactivate", "status", "budget-stats", "--help"]);
|
|
1090
1091
|
async function checkLicense() {
|
|
1091
1092
|
if (command !== void 0 && LICENSE_GATE_BYPASS_COMMANDS.has(command)) return;
|
|
1092
1093
|
const ciKey = process.env["CTXLOOM_LICENSE_KEY"];
|
|
1093
1094
|
if (ciKey) {
|
|
1094
|
-
const { ApiClient } = await import("./src-
|
|
1095
|
+
const { ApiClient } = await import("./src-KTFHRVTO.js");
|
|
1095
1096
|
const client = new ApiClient(process.env["CTXLOOM_API_BASE"]);
|
|
1096
1097
|
try {
|
|
1097
1098
|
const result = await client.validate(ciKey, "ci-ephemeral");
|
|
@@ -1496,7 +1497,7 @@ async function main() {
|
|
|
1496
1497
|
process.exit(1);
|
|
1497
1498
|
}
|
|
1498
1499
|
if (alias !== void 0) {
|
|
1499
|
-
const { validateAlias } = await import("./src-
|
|
1500
|
+
const { validateAlias } = await import("./src-KTFHRVTO.js");
|
|
1500
1501
|
const v = validateAlias(alias);
|
|
1501
1502
|
if (!v.ok) {
|
|
1502
1503
|
console.error(`[ctxloom] Invalid alias: ${v.reason}`);
|
|
@@ -1569,6 +1570,23 @@ async function main() {
|
|
|
1569
1570
|
}
|
|
1570
1571
|
break;
|
|
1571
1572
|
}
|
|
1573
|
+
case "budget-stats": {
|
|
1574
|
+
const windowArg = args.find((a) => a.startsWith("--window="))?.split("=")[1] ?? "14d";
|
|
1575
|
+
const toolArg = args.find((a) => a.startsWith("--tool="))?.split("=")[1];
|
|
1576
|
+
const days = parseInt(windowArg.replace(/d$/, ""), 10);
|
|
1577
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
1578
|
+
console.error(`[ctxloom] Invalid --window=${windowArg} \u2014 expected an integer day count like 14d`);
|
|
1579
|
+
process.exit(1);
|
|
1580
|
+
}
|
|
1581
|
+
const until = /* @__PURE__ */ new Date();
|
|
1582
|
+
const since = new Date(until.getTime() - days * 24 * 60 * 60 * 1e3);
|
|
1583
|
+
const { readEvents } = await import("./eventCollector-QSRBVUDF.js");
|
|
1584
|
+
const { summarize, renderSummary } = await import("./budgetStats-TURA232F.js");
|
|
1585
|
+
const events = readEvents({ since, until, tool: toolArg });
|
|
1586
|
+
const summary = summarize(events, since, until);
|
|
1587
|
+
console.log(renderSummary(summary));
|
|
1588
|
+
break;
|
|
1589
|
+
}
|
|
1572
1590
|
case "dashboard": {
|
|
1573
1591
|
const port = Number(
|
|
1574
1592
|
args.find((a) => a.startsWith("--port="))?.split("=")[1] ?? "7842"
|
|
@@ -1743,7 +1761,7 @@ Suggested reviewers for ${files.length} file(s):`);
|
|
|
1743
1761
|
process.stderr.write("[ctxloom] --limit must be a non-negative integer (0 for unlimited)\n");
|
|
1744
1762
|
process.exit(2);
|
|
1745
1763
|
}
|
|
1746
|
-
const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-
|
|
1764
|
+
const { loadRulesConfig, RulesChecker, formatText, formatJson, RulesConfigError } = await import("./src-KTFHRVTO.js");
|
|
1747
1765
|
let config;
|
|
1748
1766
|
try {
|
|
1749
1767
|
config = await loadRulesConfig(root);
|
|
@@ -1767,7 +1785,7 @@ Suggested reviewers for ${files.length} file(s):`);
|
|
|
1767
1785
|
}
|
|
1768
1786
|
let graph;
|
|
1769
1787
|
if (useSnapshot) {
|
|
1770
|
-
const { DependencyGraph: DG } = await import("./src-
|
|
1788
|
+
const { DependencyGraph: DG } = await import("./src-KTFHRVTO.js");
|
|
1771
1789
|
graph = new DG();
|
|
1772
1790
|
const loaded = await graph.loadSnapshotOnly(root);
|
|
1773
1791
|
if (!loaded) {
|
|
@@ -1776,7 +1794,7 @@ Suggested reviewers for ${files.length} file(s):`);
|
|
|
1776
1794
|
}
|
|
1777
1795
|
} else {
|
|
1778
1796
|
process.stderr.write("[ctxloom] Building dependency graph...\n");
|
|
1779
|
-
const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-
|
|
1797
|
+
const { ASTParser: ASTParser2, DependencyGraph: DependencyGraph2 } = await import("./src-KTFHRVTO.js");
|
|
1780
1798
|
let parser;
|
|
1781
1799
|
try {
|
|
1782
1800
|
parser = new ASTParser2();
|
|
@@ -1828,6 +1846,9 @@ Usage:
|
|
|
1828
1846
|
ctxloom dashboard Start the web dashboard (port 7842)
|
|
1829
1847
|
ctxloom dashboard --port=N Start on custom port
|
|
1830
1848
|
ctxloom dashboard --open Open browser automatically
|
|
1849
|
+
ctxloom budget-stats Aggregate Phase B budget events (per-tool p50/p75/p95)
|
|
1850
|
+
ctxloom budget-stats --window=Nd Lookback window in days (default: 14)
|
|
1851
|
+
ctxloom budget-stats --tool=NAME Restrict to one tool
|
|
1831
1852
|
ctxloom review-suggest [files] Suggest reviewers from ownership index
|
|
1832
1853
|
ctxloom authors-sync Map git emails to GitHub handles (needs GITHUB_TOKEN)
|
|
1833
1854
|
ctxloom rules check Check architecture rules (.ctxloom/rules.yml)
|
|
@@ -103,7 +103,7 @@ import {
|
|
|
103
103
|
validateDefaultRoot,
|
|
104
104
|
wrapWithIndexingEnvelope,
|
|
105
105
|
writeCODEOWNERS
|
|
106
|
-
} from "./chunk-
|
|
106
|
+
} from "./chunk-TIYTPWYN.js";
|
|
107
107
|
import {
|
|
108
108
|
VectorStore
|
|
109
109
|
} from "./chunk-DVI2RWJR.js";
|
|
@@ -113,6 +113,7 @@ import {
|
|
|
113
113
|
generateEmbedding,
|
|
114
114
|
indexDirectory
|
|
115
115
|
} from "./chunk-UVR65QBJ.js";
|
|
116
|
+
import "./chunk-5I6CJITG.js";
|
|
116
117
|
import {
|
|
117
118
|
logger
|
|
118
119
|
} from "./chunk-TYDMSHV7.js";
|
|
@@ -228,4 +229,4 @@ export {
|
|
|
228
229
|
wrapWithIndexingEnvelope,
|
|
229
230
|
writeCODEOWNERS
|
|
230
231
|
};
|
|
231
|
-
//# sourceMappingURL=src-
|
|
232
|
+
//# sourceMappingURL=src-KTFHRVTO.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ctxloom-pro",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
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",
|