context-compress 1.0.0 → 2026.3.13
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 +41 -5
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +22 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +97 -4
- package/dist/config.js.map +1 -1
- package/dist/executor.d.ts +12 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +129 -14
- package/dist/executor.js.map +1 -1
- package/dist/hooks/pretooluse.js +12 -6
- package/dist/hooks/pretooluse.js.map +1 -1
- package/dist/network.d.ts +5 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +42 -0
- package/dist/network.js.map +1 -0
- package/dist/runtime/languages/go.js +3 -3
- package/dist/runtime/languages/go.js.map +1 -1
- package/dist/runtime/languages/javascript.js +1 -1
- package/dist/runtime/languages/javascript.js.map +1 -1
- package/dist/runtime/languages/r.js +1 -1
- package/dist/runtime/languages/rust.js +2 -2
- package/dist/runtime/languages/rust.js.map +1 -1
- package/dist/runtime/languages/typescript.js +1 -1
- package/dist/runtime/languages/typescript.js.map +1 -1
- package/dist/server.bundle.mjs +531 -152
- package/dist/server.bundle.mjs.map +4 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +160 -25
- package/dist/server.js.map +1 -1
- package/dist/snippet.d.ts.map +1 -1
- package/dist/snippet.js +1 -8
- package/dist/snippet.js.map +1 -1
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +28 -11
- package/dist/stats.js.map +1 -1
- package/dist/store.d.ts +14 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +44 -22
- package/dist/store.js.map +1 -1
- package/docs/token-reduction-report.md +459 -0
- package/hooks/pretooluse.mjs +10 -5
- package/package.json +7 -4
package/dist/server.bundle.mjs
CHANGED
|
@@ -6800,85 +6800,8 @@ var require_dist = __commonJS({
|
|
|
6800
6800
|
|
|
6801
6801
|
// src/config.ts
|
|
6802
6802
|
import { readFileSync } from "node:fs";
|
|
6803
|
+
import { homedir } from "node:os";
|
|
6803
6804
|
import { join } from "node:path";
|
|
6804
|
-
var DEFAULTS = {
|
|
6805
|
-
passthroughEnvVars: [],
|
|
6806
|
-
debug: false,
|
|
6807
|
-
blockCurl: true,
|
|
6808
|
-
blockWebFetch: true,
|
|
6809
|
-
nudgeOnRead: true,
|
|
6810
|
-
nudgeOnGrep: true,
|
|
6811
|
-
intentSearchThreshold: 5e3,
|
|
6812
|
-
maxOutputBytes: 102400,
|
|
6813
|
-
hardCapBytes: 100 * 1024 * 1024,
|
|
6814
|
-
searchMaxBytes: 40960,
|
|
6815
|
-
batchMaxBytes: 81920,
|
|
6816
|
-
searchLimit: 3,
|
|
6817
|
-
searchWindowMs: 6e4,
|
|
6818
|
-
searchReduceAfter: 3,
|
|
6819
|
-
searchBlockAfter: 8
|
|
6820
|
-
};
|
|
6821
|
-
function loadFileConfig(projectDir) {
|
|
6822
|
-
const paths = [
|
|
6823
|
-
projectDir && join(projectDir, ".context-compress.json"),
|
|
6824
|
-
join(process.env.HOME ?? "~", ".context-compress.json")
|
|
6825
|
-
].filter(Boolean);
|
|
6826
|
-
for (const p of paths) {
|
|
6827
|
-
try {
|
|
6828
|
-
const raw = readFileSync(p, "utf-8");
|
|
6829
|
-
return JSON.parse(raw);
|
|
6830
|
-
} catch {
|
|
6831
|
-
}
|
|
6832
|
-
}
|
|
6833
|
-
return {};
|
|
6834
|
-
}
|
|
6835
|
-
function loadEnvConfig() {
|
|
6836
|
-
const partial2 = {};
|
|
6837
|
-
if (process.env.CONTEXT_COMPRESS_DEBUG === "1") {
|
|
6838
|
-
partial2.debug = true;
|
|
6839
|
-
}
|
|
6840
|
-
if (process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV) {
|
|
6841
|
-
partial2.passthroughEnvVars = process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6842
|
-
}
|
|
6843
|
-
if (process.env.CONTEXT_COMPRESS_BLOCK_CURL !== void 0) {
|
|
6844
|
-
partial2.blockCurl = process.env.CONTEXT_COMPRESS_BLOCK_CURL !== "0";
|
|
6845
|
-
}
|
|
6846
|
-
if (process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== void 0) {
|
|
6847
|
-
partial2.blockWebFetch = process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== "0";
|
|
6848
|
-
}
|
|
6849
|
-
if (process.env.CONTEXT_COMPRESS_NUDGE_READ !== void 0) {
|
|
6850
|
-
partial2.nudgeOnRead = process.env.CONTEXT_COMPRESS_NUDGE_READ !== "0";
|
|
6851
|
-
}
|
|
6852
|
-
if (process.env.CONTEXT_COMPRESS_NUDGE_GREP !== void 0) {
|
|
6853
|
-
partial2.nudgeOnGrep = process.env.CONTEXT_COMPRESS_NUDGE_GREP !== "0";
|
|
6854
|
-
}
|
|
6855
|
-
return partial2;
|
|
6856
|
-
}
|
|
6857
|
-
var _config = null;
|
|
6858
|
-
function loadConfig(projectDir) {
|
|
6859
|
-
if (_config) return _config;
|
|
6860
|
-
const fileConfig = loadFileConfig(projectDir);
|
|
6861
|
-
const envConfig = loadEnvConfig();
|
|
6862
|
-
_config = { ...DEFAULTS, ...fileConfig, ...envConfig };
|
|
6863
|
-
return _config;
|
|
6864
|
-
}
|
|
6865
|
-
function getConfig() {
|
|
6866
|
-
if (!_config) return loadConfig();
|
|
6867
|
-
return _config;
|
|
6868
|
-
}
|
|
6869
|
-
|
|
6870
|
-
// src/logger.ts
|
|
6871
|
-
function debug(...args) {
|
|
6872
|
-
if (getConfig().debug) {
|
|
6873
|
-
process.stderr.write(`[context-compress] ${args.map(String).join(" ")}
|
|
6874
|
-
`);
|
|
6875
|
-
}
|
|
6876
|
-
}
|
|
6877
|
-
|
|
6878
|
-
// src/server.ts
|
|
6879
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
6880
|
-
import { dirname, join as join4, resolve } from "node:path";
|
|
6881
|
-
import { fileURLToPath } from "node:url";
|
|
6882
6805
|
|
|
6883
6806
|
// node_modules/zod/v3/external.js
|
|
6884
6807
|
var external_exports = {};
|
|
@@ -10921,6 +10844,163 @@ var coerce = {
|
|
|
10921
10844
|
};
|
|
10922
10845
|
var NEVER = INVALID;
|
|
10923
10846
|
|
|
10847
|
+
// src/config.ts
|
|
10848
|
+
var DEFAULTS = {
|
|
10849
|
+
passthroughEnvVars: [],
|
|
10850
|
+
debug: false,
|
|
10851
|
+
blockCurl: true,
|
|
10852
|
+
blockWebFetch: true,
|
|
10853
|
+
nudgeOnRead: true,
|
|
10854
|
+
nudgeOnGrep: true,
|
|
10855
|
+
intentSearchThreshold: 5e3,
|
|
10856
|
+
maxOutputBytes: 102400,
|
|
10857
|
+
hardCapBytes: 100 * 1024 * 1024,
|
|
10858
|
+
searchMaxBytes: 40960,
|
|
10859
|
+
batchMaxBytes: 81920,
|
|
10860
|
+
searchLimit: 3,
|
|
10861
|
+
searchWindowMs: 6e4,
|
|
10862
|
+
searchReduceAfter: 3,
|
|
10863
|
+
searchBlockAfter: 8,
|
|
10864
|
+
compressionLevel: "normal"
|
|
10865
|
+
};
|
|
10866
|
+
var LEVEL_OVERRIDES = {
|
|
10867
|
+
normal: {},
|
|
10868
|
+
compact: {
|
|
10869
|
+
maxOutputBytes: 51200,
|
|
10870
|
+
searchMaxBytes: 20480,
|
|
10871
|
+
batchMaxBytes: 40960,
|
|
10872
|
+
searchLimit: 2,
|
|
10873
|
+
intentSearchThreshold: 3e3
|
|
10874
|
+
},
|
|
10875
|
+
ultra: {
|
|
10876
|
+
maxOutputBytes: 25600,
|
|
10877
|
+
searchMaxBytes: 10240,
|
|
10878
|
+
batchMaxBytes: 20480,
|
|
10879
|
+
searchLimit: 1,
|
|
10880
|
+
intentSearchThreshold: 2e3
|
|
10881
|
+
}
|
|
10882
|
+
};
|
|
10883
|
+
var ConfigSchema = external_exports.object({
|
|
10884
|
+
passthroughEnvVars: external_exports.array(external_exports.string()).optional(),
|
|
10885
|
+
debug: external_exports.boolean().optional(),
|
|
10886
|
+
blockCurl: external_exports.boolean().optional(),
|
|
10887
|
+
blockWebFetch: external_exports.boolean().optional(),
|
|
10888
|
+
nudgeOnRead: external_exports.boolean().optional(),
|
|
10889
|
+
nudgeOnGrep: external_exports.boolean().optional(),
|
|
10890
|
+
intentSearchThreshold: external_exports.number().int().positive().optional(),
|
|
10891
|
+
maxOutputBytes: external_exports.number().int().positive().optional(),
|
|
10892
|
+
hardCapBytes: external_exports.number().int().positive().optional(),
|
|
10893
|
+
searchMaxBytes: external_exports.number().int().positive().optional(),
|
|
10894
|
+
batchMaxBytes: external_exports.number().int().positive().optional(),
|
|
10895
|
+
searchLimit: external_exports.number().int().positive().optional(),
|
|
10896
|
+
searchWindowMs: external_exports.number().int().positive().optional(),
|
|
10897
|
+
searchReduceAfter: external_exports.number().int().nonnegative().optional(),
|
|
10898
|
+
searchBlockAfter: external_exports.number().int().positive().optional(),
|
|
10899
|
+
compressionLevel: external_exports.enum(["normal", "compact", "ultra"]).optional()
|
|
10900
|
+
});
|
|
10901
|
+
function parseIntEnv(key) {
|
|
10902
|
+
const val = process.env[key];
|
|
10903
|
+
if (val === void 0) return void 0;
|
|
10904
|
+
const n = Number.parseInt(val, 10);
|
|
10905
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
10906
|
+
}
|
|
10907
|
+
function loadFileConfig(projectDir2) {
|
|
10908
|
+
const paths = [
|
|
10909
|
+
projectDir2 && join(projectDir2, ".context-compress.json"),
|
|
10910
|
+
join(homedir(), ".context-compress.json")
|
|
10911
|
+
].filter(Boolean);
|
|
10912
|
+
for (const p of paths) {
|
|
10913
|
+
try {
|
|
10914
|
+
const raw = readFileSync(p, "utf-8");
|
|
10915
|
+
const parsed = JSON.parse(raw);
|
|
10916
|
+
const result = ConfigSchema.safeParse(parsed);
|
|
10917
|
+
if (result.success) {
|
|
10918
|
+
return result.data;
|
|
10919
|
+
}
|
|
10920
|
+
return {};
|
|
10921
|
+
} catch {
|
|
10922
|
+
}
|
|
10923
|
+
}
|
|
10924
|
+
return {};
|
|
10925
|
+
}
|
|
10926
|
+
function loadEnvConfig() {
|
|
10927
|
+
const partial2 = {};
|
|
10928
|
+
if (process.env.CONTEXT_COMPRESS_DEBUG === "1") {
|
|
10929
|
+
partial2.debug = true;
|
|
10930
|
+
}
|
|
10931
|
+
if (process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV) {
|
|
10932
|
+
partial2.passthroughEnvVars = process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10933
|
+
}
|
|
10934
|
+
if (process.env.CONTEXT_COMPRESS_BLOCK_CURL !== void 0) {
|
|
10935
|
+
partial2.blockCurl = process.env.CONTEXT_COMPRESS_BLOCK_CURL !== "0";
|
|
10936
|
+
}
|
|
10937
|
+
if (process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== void 0) {
|
|
10938
|
+
partial2.blockWebFetch = process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== "0";
|
|
10939
|
+
}
|
|
10940
|
+
if (process.env.CONTEXT_COMPRESS_NUDGE_READ !== void 0) {
|
|
10941
|
+
partial2.nudgeOnRead = process.env.CONTEXT_COMPRESS_NUDGE_READ !== "0";
|
|
10942
|
+
}
|
|
10943
|
+
if (process.env.CONTEXT_COMPRESS_NUDGE_GREP !== void 0) {
|
|
10944
|
+
partial2.nudgeOnGrep = process.env.CONTEXT_COMPRESS_NUDGE_GREP !== "0";
|
|
10945
|
+
}
|
|
10946
|
+
const maxOutput = parseIntEnv("CONTEXT_COMPRESS_MAX_OUTPUT_BYTES");
|
|
10947
|
+
if (maxOutput !== void 0) partial2.maxOutputBytes = maxOutput;
|
|
10948
|
+
const hardCap = parseIntEnv("CONTEXT_COMPRESS_HARD_CAP_BYTES");
|
|
10949
|
+
if (hardCap !== void 0) partial2.hardCapBytes = hardCap;
|
|
10950
|
+
const searchMax = parseIntEnv("CONTEXT_COMPRESS_SEARCH_MAX_BYTES");
|
|
10951
|
+
if (searchMax !== void 0) partial2.searchMaxBytes = searchMax;
|
|
10952
|
+
const batchMax = parseIntEnv("CONTEXT_COMPRESS_BATCH_MAX_BYTES");
|
|
10953
|
+
if (batchMax !== void 0) partial2.batchMaxBytes = batchMax;
|
|
10954
|
+
const searchLimit = parseIntEnv("CONTEXT_COMPRESS_SEARCH_LIMIT");
|
|
10955
|
+
if (searchLimit !== void 0) partial2.searchLimit = searchLimit;
|
|
10956
|
+
const searchWindow = parseIntEnv("CONTEXT_COMPRESS_SEARCH_WINDOW_MS");
|
|
10957
|
+
if (searchWindow !== void 0) partial2.searchWindowMs = searchWindow;
|
|
10958
|
+
const searchReduce = parseIntEnv("CONTEXT_COMPRESS_SEARCH_REDUCE_AFTER");
|
|
10959
|
+
if (searchReduce !== void 0) partial2.searchReduceAfter = searchReduce;
|
|
10960
|
+
const searchBlock = parseIntEnv("CONTEXT_COMPRESS_SEARCH_BLOCK_AFTER");
|
|
10961
|
+
if (searchBlock !== void 0) partial2.searchBlockAfter = searchBlock;
|
|
10962
|
+
const intentThreshold = parseIntEnv("CONTEXT_COMPRESS_INTENT_SEARCH_THRESHOLD");
|
|
10963
|
+
if (intentThreshold !== void 0) partial2.intentSearchThreshold = intentThreshold;
|
|
10964
|
+
const level = process.env.CONTEXT_COMPRESS_LEVEL;
|
|
10965
|
+
if (level === "normal" || level === "compact" || level === "ultra") {
|
|
10966
|
+
partial2.compressionLevel = level;
|
|
10967
|
+
}
|
|
10968
|
+
return partial2;
|
|
10969
|
+
}
|
|
10970
|
+
var _config = null;
|
|
10971
|
+
function loadConfig(projectDir2) {
|
|
10972
|
+
if (_config) return _config;
|
|
10973
|
+
const fileConfig = loadFileConfig(projectDir2);
|
|
10974
|
+
const envConfig = loadEnvConfig();
|
|
10975
|
+
const merged = { ...DEFAULTS, ...fileConfig, ...envConfig };
|
|
10976
|
+
const levelOverrides = LEVEL_OVERRIDES[merged.compressionLevel];
|
|
10977
|
+
for (const [key, value] of Object.entries(levelOverrides)) {
|
|
10978
|
+
const k = key;
|
|
10979
|
+
if (!(k in fileConfig) && !(k in envConfig)) {
|
|
10980
|
+
merged[k] = value;
|
|
10981
|
+
}
|
|
10982
|
+
}
|
|
10983
|
+
_config = merged;
|
|
10984
|
+
return _config;
|
|
10985
|
+
}
|
|
10986
|
+
function getConfig() {
|
|
10987
|
+
if (!_config) return loadConfig();
|
|
10988
|
+
return _config;
|
|
10989
|
+
}
|
|
10990
|
+
|
|
10991
|
+
// src/logger.ts
|
|
10992
|
+
function debug(...args) {
|
|
10993
|
+
if (getConfig().debug) {
|
|
10994
|
+
process.stderr.write(`[context-compress] ${args.map(String).join(" ")}
|
|
10995
|
+
`);
|
|
10996
|
+
}
|
|
10997
|
+
}
|
|
10998
|
+
|
|
10999
|
+
// src/server.ts
|
|
11000
|
+
import { readFileSync as readFileSync2, statSync } from "node:fs";
|
|
11001
|
+
import { dirname, join as join4, resolve } from "node:path";
|
|
11002
|
+
import { fileURLToPath } from "node:url";
|
|
11003
|
+
|
|
10924
11004
|
// node_modules/zod/v4/core/core.js
|
|
10925
11005
|
var NEVER2 = Object.freeze({
|
|
10926
11006
|
status: "aborted"
|
|
@@ -21073,7 +21153,6 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
21073
21153
|
import { tmpdir } from "node:os";
|
|
21074
21154
|
import { join as join2 } from "node:path";
|
|
21075
21155
|
var DEFAULT_TIMEOUT = 3e4;
|
|
21076
|
-
var DEFAULT_HARD_CAP = 100 * 1024 * 1024;
|
|
21077
21156
|
var SAFE_ENV_KEYS = [
|
|
21078
21157
|
"PATH",
|
|
21079
21158
|
"HOME",
|
|
@@ -21126,6 +21205,86 @@ function killProcessTree(pid) {
|
|
|
21126
21205
|
}
|
|
21127
21206
|
}
|
|
21128
21207
|
}
|
|
21208
|
+
function deduplicateLines(output) {
|
|
21209
|
+
const lines = output.split("\n");
|
|
21210
|
+
if (lines.length < 3) return output;
|
|
21211
|
+
const result = [];
|
|
21212
|
+
let prevLine = lines[0];
|
|
21213
|
+
let count = 1;
|
|
21214
|
+
for (let i = 1; i < lines.length; i++) {
|
|
21215
|
+
if (lines[i] === prevLine && prevLine.trim().length > 0) {
|
|
21216
|
+
count++;
|
|
21217
|
+
} else {
|
|
21218
|
+
if (count > 2) {
|
|
21219
|
+
result.push(prevLine);
|
|
21220
|
+
result.push(` ... (\xD7${count} identical lines)`);
|
|
21221
|
+
} else {
|
|
21222
|
+
for (let j = 0; j < count; j++) result.push(prevLine);
|
|
21223
|
+
}
|
|
21224
|
+
prevLine = lines[i];
|
|
21225
|
+
count = 1;
|
|
21226
|
+
}
|
|
21227
|
+
}
|
|
21228
|
+
if (count > 2) {
|
|
21229
|
+
result.push(prevLine);
|
|
21230
|
+
result.push(` ... (\xD7${count} identical lines)`);
|
|
21231
|
+
} else {
|
|
21232
|
+
for (let j = 0; j < count; j++) result.push(prevLine);
|
|
21233
|
+
}
|
|
21234
|
+
return result.join("\n");
|
|
21235
|
+
}
|
|
21236
|
+
function groupErrorLines(output) {
|
|
21237
|
+
const lines = output.split("\n");
|
|
21238
|
+
if (lines.length < 5) return output;
|
|
21239
|
+
const ERROR_RE = /^(.*?(?:error|warning|Error|Warning|ERR|WARN)[:\s])\s*(.+?)(?:\s+(?:at|in|on)\s+(?:line\s+)?(\d+))?$/i;
|
|
21240
|
+
const errorGroups = /* @__PURE__ */ new Map();
|
|
21241
|
+
const resultLines = [];
|
|
21242
|
+
let groupedCount = 0;
|
|
21243
|
+
for (const line of lines) {
|
|
21244
|
+
const match = line.match(ERROR_RE);
|
|
21245
|
+
if (match) {
|
|
21246
|
+
const prefix = match[1].trim();
|
|
21247
|
+
const msg = match[2].trim();
|
|
21248
|
+
const key = `${prefix}|${msg}`;
|
|
21249
|
+
const existing = errorGroups.get(key);
|
|
21250
|
+
if (existing) {
|
|
21251
|
+
existing.count++;
|
|
21252
|
+
if (match[3]) existing.locations.push(match[3]);
|
|
21253
|
+
groupedCount++;
|
|
21254
|
+
continue;
|
|
21255
|
+
}
|
|
21256
|
+
errorGroups.set(key, {
|
|
21257
|
+
message: `${prefix} ${msg}`,
|
|
21258
|
+
locations: match[3] ? [match[3]] : [],
|
|
21259
|
+
count: 1
|
|
21260
|
+
});
|
|
21261
|
+
groupedCount++;
|
|
21262
|
+
continue;
|
|
21263
|
+
}
|
|
21264
|
+
resultLines.push(line);
|
|
21265
|
+
}
|
|
21266
|
+
if (groupedCount < 4 || errorGroups.size === groupedCount) return output;
|
|
21267
|
+
const grouped = [];
|
|
21268
|
+
for (const [, group] of errorGroups) {
|
|
21269
|
+
if (group.count === 1) {
|
|
21270
|
+
grouped.push(
|
|
21271
|
+
group.message + (group.locations.length ? ` at line ${group.locations[0]}` : "")
|
|
21272
|
+
);
|
|
21273
|
+
} else {
|
|
21274
|
+
let line = `${group.message} (\xD7${group.count})`;
|
|
21275
|
+
if (group.locations.length > 0) {
|
|
21276
|
+
line += ` [lines: ${group.locations.join(", ")}]`;
|
|
21277
|
+
}
|
|
21278
|
+
grouped.push(line);
|
|
21279
|
+
}
|
|
21280
|
+
}
|
|
21281
|
+
if (grouped.length > 0) {
|
|
21282
|
+
resultLines.push("");
|
|
21283
|
+
resultLines.push(`\u2500\u2500 Grouped errors/warnings (${groupedCount} \u2192 ${errorGroups.size}) \u2500\u2500`);
|
|
21284
|
+
resultLines.push(...grouped);
|
|
21285
|
+
}
|
|
21286
|
+
return resultLines.join("\n");
|
|
21287
|
+
}
|
|
21129
21288
|
function smartTruncate(output, maxBytes) {
|
|
21130
21289
|
if (Buffer.byteLength(output) <= maxBytes) return output;
|
|
21131
21290
|
const lines = output.split("\n");
|
|
@@ -21153,11 +21312,11 @@ function smartTruncate(output, maxBytes) {
|
|
|
21153
21312
|
const truncatedLines = lines.length - headEnd - (lines.length - tailStart);
|
|
21154
21313
|
const truncatedBytes = Buffer.byteLength(output) - headBytes - tailBytes;
|
|
21155
21314
|
const separator = `
|
|
21156
|
-
... [${truncatedLines} lines / ${
|
|
21315
|
+
... [${truncatedLines} lines / ${formatBytes(truncatedBytes)} truncated \u2014 showing first ${headEnd} + last ${lines.length - tailStart} lines] ...
|
|
21157
21316
|
`;
|
|
21158
21317
|
return headLines.join("\n") + separator + tailLines.join("\n");
|
|
21159
21318
|
}
|
|
21160
|
-
function
|
|
21319
|
+
function formatBytes(bytes) {
|
|
21161
21320
|
if (bytes < 1024) return `${bytes}B`;
|
|
21162
21321
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
21163
21322
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
@@ -21231,7 +21390,7 @@ var SubprocessExecutor = class {
|
|
|
21231
21390
|
plugin.needsShell
|
|
21232
21391
|
);
|
|
21233
21392
|
} finally {
|
|
21234
|
-
setTimeout(() => this.cleanupTempDir(tmpDir), 100);
|
|
21393
|
+
setTimeout(() => this.cleanupTempDir(tmpDir), 100).unref();
|
|
21235
21394
|
}
|
|
21236
21395
|
}
|
|
21237
21396
|
/**
|
|
@@ -21258,11 +21417,12 @@ var SubprocessExecutor = class {
|
|
|
21258
21417
|
spawnAndCapture(cmd, args, cwd, timeout, maxOutput, useShell) {
|
|
21259
21418
|
return new Promise((resolve2) => {
|
|
21260
21419
|
const hardCap = this.config.hardCapBytes;
|
|
21261
|
-
|
|
21262
|
-
|
|
21420
|
+
const stdoutChunks = [];
|
|
21421
|
+
const stderrChunks = [];
|
|
21263
21422
|
let totalBytes = 0;
|
|
21264
21423
|
let killed = false;
|
|
21265
21424
|
let networkBytes;
|
|
21425
|
+
let resolved = false;
|
|
21266
21426
|
const proc = spawn(cmd, args, {
|
|
21267
21427
|
cwd,
|
|
21268
21428
|
env: { ...this.env, TMPDIR: cwd },
|
|
@@ -21278,7 +21438,7 @@ var SubprocessExecutor = class {
|
|
|
21278
21438
|
if (proc.pid) killProcessTree(proc.pid);
|
|
21279
21439
|
return;
|
|
21280
21440
|
}
|
|
21281
|
-
|
|
21441
|
+
stdoutChunks.push(chunk);
|
|
21282
21442
|
});
|
|
21283
21443
|
proc.stderr?.on("data", (chunk) => {
|
|
21284
21444
|
totalBytes += chunk.length;
|
|
@@ -21287,14 +21447,26 @@ var SubprocessExecutor = class {
|
|
|
21287
21447
|
if (proc.pid) killProcessTree(proc.pid);
|
|
21288
21448
|
return;
|
|
21289
21449
|
}
|
|
21290
|
-
|
|
21450
|
+
stderrChunks.push(chunk);
|
|
21291
21451
|
});
|
|
21292
21452
|
proc.on("error", (err) => {
|
|
21293
21453
|
debug("Process error:", err.message);
|
|
21454
|
+
if (!resolved) {
|
|
21455
|
+
resolved = true;
|
|
21456
|
+
resolve2({
|
|
21457
|
+
stdout: "",
|
|
21458
|
+
stderr: err.message,
|
|
21459
|
+
exitCode: 1,
|
|
21460
|
+
truncated: false,
|
|
21461
|
+
killed: false
|
|
21462
|
+
});
|
|
21463
|
+
}
|
|
21294
21464
|
});
|
|
21295
21465
|
proc.on("close", (code) => {
|
|
21296
|
-
|
|
21297
|
-
|
|
21466
|
+
if (resolved) return;
|
|
21467
|
+
resolved = true;
|
|
21468
|
+
let stdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
21469
|
+
let stderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
21298
21470
|
const netMatch = stderr.match(/__CM_NET__:(\d+)/);
|
|
21299
21471
|
if (netMatch) {
|
|
21300
21472
|
networkBytes = Number.parseInt(netMatch[1], 10);
|
|
@@ -21302,8 +21474,10 @@ var SubprocessExecutor = class {
|
|
|
21302
21474
|
}
|
|
21303
21475
|
if (killed) {
|
|
21304
21476
|
stdout += `
|
|
21305
|
-
[output capped at ${
|
|
21477
|
+
[output capped at ${formatBytes(hardCap)} \u2014 process killed]`;
|
|
21306
21478
|
}
|
|
21479
|
+
stdout = deduplicateLines(stdout);
|
|
21480
|
+
stdout = groupErrorLines(stdout);
|
|
21307
21481
|
const truncated = Buffer.byteLength(stdout) > maxOutput;
|
|
21308
21482
|
if (truncated) {
|
|
21309
21483
|
stdout = smartTruncate(stdout, maxOutput);
|
|
@@ -21333,7 +21507,7 @@ var SubprocessExecutor = class {
|
|
|
21333
21507
|
}
|
|
21334
21508
|
};
|
|
21335
21509
|
function wrapWithNetworkTracking(code) {
|
|
21336
|
-
const preamble = "let __cm_net=0;const __cm_f=globalThis.fetch;if(__cm_f){globalThis.fetch=async(...a)=>{const r=await __cm_f(...a);try{const cl=r.
|
|
21510
|
+
const preamble = "let __cm_net=0;const __cm_f=globalThis.fetch;if(__cm_f){globalThis.fetch=async(...a)=>{const r=await __cm_f(...a);try{const cl=r.headers.get('content-length');if(cl){__cm_net+=parseInt(cl,10)}else{const b=await r.clone().arrayBuffer();__cm_net+=b.byteLength}}catch{}return r};}";
|
|
21337
21511
|
const epilogue = `
|
|
21338
21512
|
process.stderr.write('__CM_NET__:'+__cm_net+'\\n');`;
|
|
21339
21513
|
return `${preamble}
|
|
@@ -21341,6 +21515,25 @@ async function __cm_main(){${code}}
|
|
|
21341
21515
|
__cm_main().then(()=>{${epilogue}}).catch(e=>{console.error(e);${epilogue}process.exit(1)});`;
|
|
21342
21516
|
}
|
|
21343
21517
|
|
|
21518
|
+
// src/network.ts
|
|
21519
|
+
function isPrivateHost(hostname2) {
|
|
21520
|
+
const h = hostname2.startsWith("[") && hostname2.endsWith("]") ? hostname2.slice(1, -1) : hostname2;
|
|
21521
|
+
const lower = h.toLowerCase();
|
|
21522
|
+
if (lower === "localhost" || lower === "0.0.0.0") return true;
|
|
21523
|
+
if (/^127\./.test(h)) return true;
|
|
21524
|
+
if (/^10\./.test(h)) return true;
|
|
21525
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
|
|
21526
|
+
if (/^192\.168\./.test(h)) return true;
|
|
21527
|
+
if (/^169\.254\./.test(h)) return true;
|
|
21528
|
+
if (/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(h)) return true;
|
|
21529
|
+
if (lower === "::1") return true;
|
|
21530
|
+
const mappedMatch = lower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
21531
|
+
if (mappedMatch) return isPrivateHost(mappedMatch[1]);
|
|
21532
|
+
if (/^fe[89ab]/i.test(h)) return true;
|
|
21533
|
+
if (/^f[cd]/i.test(h)) return true;
|
|
21534
|
+
return false;
|
|
21535
|
+
}
|
|
21536
|
+
|
|
21344
21537
|
// src/runtime/index.ts
|
|
21345
21538
|
import { exec } from "node:child_process";
|
|
21346
21539
|
import { promisify } from "node:util";
|
|
@@ -21372,8 +21565,8 @@ var goPlugin = {
|
|
|
21372
21565
|
return [runtime, "run", filePath];
|
|
21373
21566
|
},
|
|
21374
21567
|
preprocessCode(code) {
|
|
21375
|
-
if (
|
|
21376
|
-
const hasImport =
|
|
21568
|
+
if (!/^package\s/m.test(code)) {
|
|
21569
|
+
const hasImport = /^import\s/m.test(code);
|
|
21377
21570
|
if (hasImport) {
|
|
21378
21571
|
return `package main
|
|
21379
21572
|
|
|
@@ -21392,7 +21585,7 @@ _ = fmt.Sprintf("")
|
|
|
21392
21585
|
},
|
|
21393
21586
|
wrapWithFileContent(code, filePath) {
|
|
21394
21587
|
const escaped = JSON.stringify(filePath);
|
|
21395
|
-
const hasPackage =
|
|
21588
|
+
const hasPackage = /^package\s/m.test(code);
|
|
21396
21589
|
if (hasPackage) {
|
|
21397
21590
|
return code.replace(
|
|
21398
21591
|
/(package\s+\w+\n)/,
|
|
@@ -21433,8 +21626,9 @@ var javascriptPlugin = {
|
|
|
21433
21626
|
},
|
|
21434
21627
|
wrapWithFileContent(code, filePath) {
|
|
21435
21628
|
const escaped = JSON.stringify(filePath);
|
|
21436
|
-
return `const
|
|
21437
|
-
const
|
|
21629
|
+
return `const {readFileSync: __cm_readFileSync} = await import("node:fs");
|
|
21630
|
+
const FILE_CONTENT_PATH = ${escaped};
|
|
21631
|
+
const FILE_CONTENT = __cm_readFileSync(FILE_CONTENT_PATH, "utf-8");
|
|
21438
21632
|
${code}`;
|
|
21439
21633
|
}
|
|
21440
21634
|
};
|
|
@@ -21506,7 +21700,7 @@ ${code}`;
|
|
|
21506
21700
|
// src/runtime/languages/r.ts
|
|
21507
21701
|
var rPlugin = {
|
|
21508
21702
|
language: "r",
|
|
21509
|
-
runtimeCandidates: ["Rscript", "
|
|
21703
|
+
runtimeCandidates: ["Rscript", "R"],
|
|
21510
21704
|
fileExtension: ".R",
|
|
21511
21705
|
buildCommand(runtime, filePath) {
|
|
21512
21706
|
return [runtime, filePath];
|
|
@@ -21549,7 +21743,7 @@ var rustPlugin = {
|
|
|
21549
21743
|
return [runtime, srcPath, "-o", binPath];
|
|
21550
21744
|
},
|
|
21551
21745
|
preprocessCode(code) {
|
|
21552
|
-
if (
|
|
21746
|
+
if (!/^fn\s+main\s*\(/m.test(code)) {
|
|
21553
21747
|
return `fn main() {
|
|
21554
21748
|
${code}
|
|
21555
21749
|
}`;
|
|
@@ -21562,7 +21756,7 @@ ${code}
|
|
|
21562
21756
|
let file_content_path = ${escaped};
|
|
21563
21757
|
let file_content = fs::read_to_string(file_content_path).unwrap();
|
|
21564
21758
|
`;
|
|
21565
|
-
if (
|
|
21759
|
+
if (/^fn\s+main\s*\(/m.test(code)) {
|
|
21566
21760
|
return code.replace(/fn main\s*\(\s*\)\s*\{/, `fn main() {
|
|
21567
21761
|
${preamble}`);
|
|
21568
21762
|
}
|
|
@@ -21598,8 +21792,9 @@ var typescriptPlugin = {
|
|
|
21598
21792
|
},
|
|
21599
21793
|
wrapWithFileContent(code, filePath) {
|
|
21600
21794
|
const escaped = JSON.stringify(filePath);
|
|
21601
|
-
return `const
|
|
21602
|
-
const
|
|
21795
|
+
return `const {readFileSync: __cm_readFileSync} = await import("node:fs");
|
|
21796
|
+
const FILE_CONTENT_PATH = ${escaped};
|
|
21797
|
+
const FILE_CONTENT = __cm_readFileSync(FILE_CONTENT_PATH, "utf-8");
|
|
21603
21798
|
${code}`;
|
|
21604
21799
|
},
|
|
21605
21800
|
// tsx and ts-node may be .cmd shims on Windows
|
|
@@ -21655,6 +21850,16 @@ function hasBun(runtimes) {
|
|
|
21655
21850
|
}
|
|
21656
21851
|
|
|
21657
21852
|
// src/stats.ts
|
|
21853
|
+
var BAR_WIDTH = 20;
|
|
21854
|
+
function asciiBar(ratio, width = BAR_WIDTH) {
|
|
21855
|
+
const filled = Math.round(ratio * width);
|
|
21856
|
+
const empty = width - filled;
|
|
21857
|
+
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}] ${(ratio * 100).toFixed(0)}%`;
|
|
21858
|
+
}
|
|
21859
|
+
function tokenCost(tokens) {
|
|
21860
|
+
const cost = tokens / 1e6 * 3;
|
|
21861
|
+
return cost >= 0.01 ? `~$${cost.toFixed(2)}` : "<$0.01";
|
|
21862
|
+
}
|
|
21658
21863
|
var SessionTracker = class {
|
|
21659
21864
|
stats = {
|
|
21660
21865
|
calls: {},
|
|
@@ -21688,6 +21893,7 @@ var SessionTracker = class {
|
|
|
21688
21893
|
const savingsRatio = totalReturned > 0 ? totalProcessed / totalReturned : 1;
|
|
21689
21894
|
const reductionPct = totalProcessed > 0 ? ((1 - totalReturned / totalProcessed) * 100).toFixed(1) : "0.0";
|
|
21690
21895
|
const estTokens = Math.round(totalReturned / 4);
|
|
21896
|
+
const estTokensAvoided = Math.round(keptOut / 4);
|
|
21691
21897
|
const lines = [];
|
|
21692
21898
|
lines.push("## Session Statistics\n");
|
|
21693
21899
|
lines.push("| Metric | Value |");
|
|
@@ -21697,18 +21903,31 @@ var SessionTracker = class {
|
|
|
21697
21903
|
lines.push(`| Total data processed | ${formatBytes(totalProcessed)} |`);
|
|
21698
21904
|
lines.push(`| Kept in sandbox | ${formatBytes(keptOut)} |`);
|
|
21699
21905
|
lines.push(`| Context consumed | ${formatBytes(totalReturned)} |`);
|
|
21700
|
-
lines.push(`| Est. tokens | ~${estTokens.toLocaleString()} |`);
|
|
21906
|
+
lines.push(`| Est. tokens used | ~${estTokens.toLocaleString()} (${tokenCost(estTokens)}) |`);
|
|
21907
|
+
lines.push(
|
|
21908
|
+
`| Est. tokens saved | ~${estTokensAvoided.toLocaleString()} (${tokenCost(estTokensAvoided)}) |`
|
|
21909
|
+
);
|
|
21701
21910
|
lines.push(
|
|
21702
21911
|
`| **Savings ratio** | **${savingsRatio.toFixed(1)}x** (${reductionPct}% reduction) |`
|
|
21703
21912
|
);
|
|
21913
|
+
if (totalProcessed > 0) {
|
|
21914
|
+
const savingsBar = asciiBar(keptOut / totalProcessed);
|
|
21915
|
+
lines.push(`
|
|
21916
|
+
**Context savings:** ${savingsBar}`);
|
|
21917
|
+
lines.push(
|
|
21918
|
+
` Sandbox: ${formatBytes(keptOut)} kept out | Context: ${formatBytes(totalReturned)} entered`
|
|
21919
|
+
);
|
|
21920
|
+
}
|
|
21704
21921
|
if (totalCalls > 0) {
|
|
21705
21922
|
lines.push("\n## Per-Tool Breakdown\n");
|
|
21706
|
-
|
|
21707
|
-
lines.push("|------|-------|--------------|-------------|");
|
|
21923
|
+
const maxBytes = Math.max(...Object.values(snap.bytesReturned));
|
|
21708
21924
|
for (const [name, calls] of Object.entries(snap.calls)) {
|
|
21709
21925
|
const bytes = snap.bytesReturned[name] ?? 0;
|
|
21926
|
+
const tokens = Math.round(bytes / 4);
|
|
21927
|
+
const barRatio = maxBytes > 0 ? bytes / maxBytes : 0;
|
|
21928
|
+
const bar = "\u2588".repeat(Math.max(1, Math.round(barRatio * 15)));
|
|
21710
21929
|
lines.push(
|
|
21711
|
-
|
|
21930
|
+
` ${name.padEnd(16)} ${String(calls).padStart(3)} calls ${bar} ${formatBytes(bytes)} (~${tokens.toLocaleString()} tok)`
|
|
21712
21931
|
);
|
|
21713
21932
|
}
|
|
21714
21933
|
}
|
|
@@ -21719,11 +21938,6 @@ Context-compress kept ${formatBytes(keptOut)} out of context (${reductionPct}% s
|
|
|
21719
21938
|
return lines.join("\n");
|
|
21720
21939
|
}
|
|
21721
21940
|
};
|
|
21722
|
-
function formatBytes(bytes) {
|
|
21723
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
21724
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
21725
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
21726
|
-
}
|
|
21727
21941
|
|
|
21728
21942
|
// src/store.ts
|
|
21729
21943
|
import { readdirSync, unlinkSync } from "node:fs";
|
|
@@ -21754,14 +21968,7 @@ function positionsFromHighlight(highlighted) {
|
|
|
21754
21968
|
return positions;
|
|
21755
21969
|
}
|
|
21756
21970
|
function stripMarkers(text) {
|
|
21757
|
-
|
|
21758
|
-
for (let i = 0; i < text.length; i++) {
|
|
21759
|
-
const ch = text[i];
|
|
21760
|
-
if (ch !== STX && ch !== ETX) {
|
|
21761
|
-
result += ch;
|
|
21762
|
-
}
|
|
21763
|
-
}
|
|
21764
|
-
return result;
|
|
21971
|
+
return text.replaceAll(STX, "").replaceAll(ETX, "");
|
|
21765
21972
|
}
|
|
21766
21973
|
function extractSnippet(highlighted, maxLen = DEFAULT_MAX_LEN) {
|
|
21767
21974
|
const positions = positionsFromHighlight(highlighted);
|
|
@@ -21916,7 +22123,7 @@ var WORD_SPLIT_RE = /[^\p{L}\p{N}_-]+/u;
|
|
|
21916
22123
|
function sanitizeQuery(raw) {
|
|
21917
22124
|
const q = raw.replace(FTS_SPECIAL_RE, " ").replace(FTS_OPERATORS_RE, " ").trim();
|
|
21918
22125
|
const words = q.split(/\s+/).filter((w) => w.length >= 2).map((w) => `"${w}"`);
|
|
21919
|
-
return words.length > 0 ? words.join(" OR ") :
|
|
22126
|
+
return words.length > 0 ? words.join(" OR ") : "";
|
|
21920
22127
|
}
|
|
21921
22128
|
function levenshtein(a, b) {
|
|
21922
22129
|
if (a.length === 0) return b.length;
|
|
@@ -21936,6 +22143,11 @@ function levenshtein(a, b) {
|
|
|
21936
22143
|
var ContentStore = class {
|
|
21937
22144
|
db;
|
|
21938
22145
|
hasTrigramTable = false;
|
|
22146
|
+
// Cached prepared statements (initialized in initSchema, always available after constructor)
|
|
22147
|
+
insertSourceStmt;
|
|
22148
|
+
insertChunkStmt;
|
|
22149
|
+
vocabCountStmt;
|
|
22150
|
+
vocabInsertStmt;
|
|
21939
22151
|
constructor(dbPath) {
|
|
21940
22152
|
const path = dbPath ?? join3(tmpdir2(), `context-compress-${process.pid}.db`);
|
|
21941
22153
|
this.db = new Database(path);
|
|
@@ -21965,6 +22177,14 @@ var ContentStore = class {
|
|
|
21965
22177
|
word TEXT PRIMARY KEY
|
|
21966
22178
|
);
|
|
21967
22179
|
`);
|
|
22180
|
+
this.insertSourceStmt = this.db.prepare(
|
|
22181
|
+
"INSERT INTO sources (label, chunk_count, code_chunk_count) VALUES (?, ?, ?)"
|
|
22182
|
+
);
|
|
22183
|
+
this.insertChunkStmt = this.db.prepare(
|
|
22184
|
+
"INSERT INTO chunks (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
|
|
22185
|
+
);
|
|
22186
|
+
this.vocabCountStmt = this.db.prepare("SELECT COUNT(*) as cnt FROM vocabulary");
|
|
22187
|
+
this.vocabInsertStmt = this.db.prepare("INSERT OR IGNORE INTO vocabulary (word) VALUES (?)");
|
|
21968
22188
|
}
|
|
21969
22189
|
/** Lazily create trigram table only when porter search returns 0 results */
|
|
21970
22190
|
ensureTrigramTable() {
|
|
@@ -21979,16 +22199,9 @@ var ContentStore = class {
|
|
|
21979
22199
|
tokenize='trigram'
|
|
21980
22200
|
);
|
|
21981
22201
|
`);
|
|
21982
|
-
|
|
21983
|
-
|
|
21984
|
-
"INSERT INTO chunks_trigram (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
|
|
22202
|
+
this.db.exec(
|
|
22203
|
+
"INSERT INTO chunks_trigram (title, content, source_id, content_type) SELECT title, content, source_id, content_type FROM chunks"
|
|
21985
22204
|
);
|
|
21986
|
-
const tx = this.db.transaction(() => {
|
|
21987
|
-
for (const row of rows) {
|
|
21988
|
-
insert.run(row.title, row.content, row.source_id, row.content_type);
|
|
21989
|
-
}
|
|
21990
|
-
});
|
|
21991
|
-
tx();
|
|
21992
22205
|
this.hasTrigramTable = true;
|
|
21993
22206
|
}
|
|
21994
22207
|
/**
|
|
@@ -21997,12 +22210,8 @@ var ContentStore = class {
|
|
|
21997
22210
|
index(content, label) {
|
|
21998
22211
|
const isMarkdown = HEADING_RE.test(content) || content.includes("```") || content.includes("---");
|
|
21999
22212
|
const chunks = isMarkdown ? chunkMarkdown(content) : chunkPlainText(content);
|
|
22000
|
-
const insertSource = this.
|
|
22001
|
-
|
|
22002
|
-
);
|
|
22003
|
-
const insertChunk = this.db.prepare(
|
|
22004
|
-
"INSERT INTO chunks (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
|
|
22005
|
-
);
|
|
22213
|
+
const insertSource = this.insertSourceStmt;
|
|
22214
|
+
const insertChunk = this.insertChunkStmt;
|
|
22006
22215
|
const insertTrigram = this.hasTrigramTable ? this.db.prepare(
|
|
22007
22216
|
"INSERT INTO chunks_trigram (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
|
|
22008
22217
|
) : null;
|
|
@@ -22034,6 +22243,9 @@ var ContentStore = class {
|
|
|
22034
22243
|
search(query, options) {
|
|
22035
22244
|
const limit = options?.limit ?? 3;
|
|
22036
22245
|
const sanitized = sanitizeQuery(query);
|
|
22246
|
+
if (!sanitized) {
|
|
22247
|
+
return { query, results: [] };
|
|
22248
|
+
}
|
|
22037
22249
|
let hits = this.porterSearch(sanitized, options?.source, limit);
|
|
22038
22250
|
if (hits.length > 0) {
|
|
22039
22251
|
return { query, results: hits };
|
|
@@ -22046,9 +22258,11 @@ var ContentStore = class {
|
|
|
22046
22258
|
const corrected = this.fuzzyCorrect(query);
|
|
22047
22259
|
if (corrected && corrected !== query) {
|
|
22048
22260
|
const correctedSanitized = sanitizeQuery(corrected);
|
|
22049
|
-
|
|
22050
|
-
|
|
22051
|
-
|
|
22261
|
+
if (correctedSanitized) {
|
|
22262
|
+
hits = this.porterSearch(correctedSanitized, options?.source, limit);
|
|
22263
|
+
if (hits.length > 0) {
|
|
22264
|
+
return { query, results: hits, corrected };
|
|
22265
|
+
}
|
|
22052
22266
|
}
|
|
22053
22267
|
}
|
|
22054
22268
|
return { query, results: [] };
|
|
@@ -22129,7 +22343,7 @@ var ContentStore = class {
|
|
|
22129
22343
|
const maxDist = word.length <= 4 ? 1 : word.length <= 12 ? 2 : 3;
|
|
22130
22344
|
const minLen = word.length - maxDist;
|
|
22131
22345
|
const maxLen = word.length + maxDist;
|
|
22132
|
-
const candidates = this.db.prepare("SELECT word FROM vocabulary WHERE length(word) BETWEEN ? AND ?").all(minLen, maxLen);
|
|
22346
|
+
const candidates = this.db.prepare("SELECT word FROM vocabulary WHERE length(word) BETWEEN ? AND ? LIMIT 500").all(minLen, maxLen);
|
|
22133
22347
|
let bestWord = word;
|
|
22134
22348
|
let bestDist = maxDist + 1;
|
|
22135
22349
|
for (const { word: candidate } of candidates) {
|
|
@@ -22148,11 +22362,11 @@ var ContentStore = class {
|
|
|
22148
22362
|
* Update vocabulary table from content (bounded to MAX_VOCABULARY).
|
|
22149
22363
|
*/
|
|
22150
22364
|
updateVocabulary(content) {
|
|
22151
|
-
const currentCount = this.
|
|
22365
|
+
const currentCount = this.vocabCountStmt.get().cnt;
|
|
22152
22366
|
if (currentCount >= MAX_VOCABULARY) return;
|
|
22153
22367
|
const words = content.split(WORD_SPLIT_RE).filter((w) => w.length >= 3 && !STOPWORDS.has(w.toLowerCase()));
|
|
22154
22368
|
const unique = new Set(words.map((w) => w.toLowerCase()));
|
|
22155
|
-
const insert = this.
|
|
22369
|
+
const insert = this.vocabInsertStmt;
|
|
22156
22370
|
let added = 0;
|
|
22157
22371
|
for (const word of unique) {
|
|
22158
22372
|
if (currentCount + added >= MAX_VOCABULARY) break;
|
|
@@ -22169,7 +22383,7 @@ var ContentStore = class {
|
|
|
22169
22383
|
).get(...sourceId ? [sourceId] : []).cnt;
|
|
22170
22384
|
if (totalChunks === 0) return [];
|
|
22171
22385
|
const filter = sourceId ? " WHERE source_id = ?" : "";
|
|
22172
|
-
const stmt = this.db.prepare(`SELECT content FROM chunks${filter}`);
|
|
22386
|
+
const stmt = this.db.prepare(`SELECT content FROM chunks${filter} LIMIT 500`);
|
|
22173
22387
|
const rows = sourceId ? stmt.all(sourceId) : stmt.all();
|
|
22174
22388
|
const docFreq = /* @__PURE__ */ new Map();
|
|
22175
22389
|
for (const row of rows) {
|
|
@@ -22194,6 +22408,21 @@ var ContentStore = class {
|
|
|
22194
22408
|
scored.sort((a, b) => b.score - a.score);
|
|
22195
22409
|
return scored.slice(0, 40).map((s) => s.word);
|
|
22196
22410
|
}
|
|
22411
|
+
/**
|
|
22412
|
+
* List all indexed sources with metadata.
|
|
22413
|
+
*/
|
|
22414
|
+
listSources() {
|
|
22415
|
+
const rows = this.db.prepare(
|
|
22416
|
+
"SELECT id, label, chunk_count, code_chunk_count, indexed_at FROM sources ORDER BY indexed_at DESC"
|
|
22417
|
+
).all();
|
|
22418
|
+
return rows.map((row) => ({
|
|
22419
|
+
id: row.id,
|
|
22420
|
+
label: row.label,
|
|
22421
|
+
chunkCount: row.chunk_count,
|
|
22422
|
+
codeChunks: row.code_chunk_count,
|
|
22423
|
+
indexedAt: row.indexed_at
|
|
22424
|
+
}));
|
|
22425
|
+
}
|
|
22197
22426
|
/**
|
|
22198
22427
|
* Get store statistics.
|
|
22199
22428
|
*/
|
|
@@ -22319,8 +22548,8 @@ function cleanupStaleDbs() {
|
|
|
22319
22548
|
return cleaned;
|
|
22320
22549
|
}
|
|
22321
22550
|
|
|
22322
|
-
// src/
|
|
22323
|
-
var
|
|
22551
|
+
// src/types.ts
|
|
22552
|
+
var ALL_LANGUAGES = [
|
|
22324
22553
|
"javascript",
|
|
22325
22554
|
"typescript",
|
|
22326
22555
|
"python",
|
|
@@ -22333,6 +22562,14 @@ var LANGUAGES = [
|
|
|
22333
22562
|
"r",
|
|
22334
22563
|
"elixir"
|
|
22335
22564
|
];
|
|
22565
|
+
|
|
22566
|
+
// src/server.ts
|
|
22567
|
+
var LANGUAGE_ENUM = ALL_LANGUAGES;
|
|
22568
|
+
var projectDir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
22569
|
+
function isWithinProject(absPath) {
|
|
22570
|
+
const normalized = resolve(absPath);
|
|
22571
|
+
return normalized === projectDir || normalized.startsWith(`${projectDir}/`);
|
|
22572
|
+
}
|
|
22336
22573
|
function getVersion() {
|
|
22337
22574
|
try {
|
|
22338
22575
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -22343,6 +22580,18 @@ function getVersion() {
|
|
|
22343
22580
|
return "1.0.0";
|
|
22344
22581
|
}
|
|
22345
22582
|
}
|
|
22583
|
+
function compactLabel(normal, level) {
|
|
22584
|
+
if (level === "ultra") {
|
|
22585
|
+
return normal.replace(/\*\*/g, "").replace(/Use search\(queries: \[\.\.\.]\) to retrieve.*$/gm, "\u2192 search() for more").replace(/Searchable terms: .+$/gm, "");
|
|
22586
|
+
}
|
|
22587
|
+
if (level === "compact") {
|
|
22588
|
+
return normal.replace(
|
|
22589
|
+
/Use search\(queries: \[\.\.\.]\) to retrieve full content of any section\./,
|
|
22590
|
+
"\u2192 search() for details"
|
|
22591
|
+
);
|
|
22592
|
+
}
|
|
22593
|
+
return normal;
|
|
22594
|
+
}
|
|
22346
22595
|
async function createServer(config3) {
|
|
22347
22596
|
const version2 = getVersion();
|
|
22348
22597
|
debug("Version:", version2);
|
|
@@ -22353,6 +22602,15 @@ async function createServer(config3) {
|
|
|
22353
22602
|
const executor = new SubprocessExecutor(runtimes, config3);
|
|
22354
22603
|
const store = new ContentStore();
|
|
22355
22604
|
const tracker = new SessionTracker();
|
|
22605
|
+
const shutdown = () => {
|
|
22606
|
+
try {
|
|
22607
|
+
store.close();
|
|
22608
|
+
} catch {
|
|
22609
|
+
}
|
|
22610
|
+
};
|
|
22611
|
+
process.on("SIGINT", shutdown);
|
|
22612
|
+
process.on("SIGTERM", shutdown);
|
|
22613
|
+
process.on("beforeExit", shutdown);
|
|
22356
22614
|
const searchCalls = [];
|
|
22357
22615
|
const server2 = new McpServer({
|
|
22358
22616
|
name: "context-compress",
|
|
@@ -22360,11 +22618,11 @@ async function createServer(config3) {
|
|
|
22360
22618
|
});
|
|
22361
22619
|
server2.tool(
|
|
22362
22620
|
"execute",
|
|
22363
|
-
`Execute code in a sandboxed subprocess. Only stdout enters context \u2014 raw data stays in the subprocess. Use instead of bash/cat when output would exceed 20 lines. ${bunDetected ? "(Bun detected \u2014 JS/TS runs 3-5x faster) " : ""}Available: ${
|
|
22621
|
+
`Execute code in a sandboxed subprocess. Only stdout enters context \u2014 raw data stays in the subprocess. Use instead of bash/cat when output would exceed 20 lines. ${bunDetected ? "(Bun detected \u2014 JS/TS runs 3-5x faster) " : ""}Available: ${ALL_LANGUAGES.join(", ")}.
|
|
22364
22622
|
|
|
22365
22623
|
PREFER THIS OVER BASH for: API calls (gh, curl, aws), test runners (npm test, pytest), git queries (git log, git diff), data processing, and ANY CLI command that may produce large output. Bash should only be used for file mutations, git writes, and navigation.`,
|
|
22366
22624
|
{
|
|
22367
|
-
language: external_exports.enum(
|
|
22625
|
+
language: external_exports.enum(LANGUAGE_ENUM).describe("Runtime language"),
|
|
22368
22626
|
code: external_exports.string().describe(
|
|
22369
22627
|
"Source code to execute. Use console.log (JS/TS), print (Python/Ruby/Perl/R), echo (Shell), echo (PHP), fmt.Println (Go), or IO.puts (Elixir) to output a summary to context."
|
|
22370
22628
|
),
|
|
@@ -22399,13 +22657,13 @@ ${result.stderr}`;
|
|
|
22399
22657
|
filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}
|
|
22400
22658
|
`;
|
|
22401
22659
|
}
|
|
22402
|
-
if (terms.length > 0) {
|
|
22660
|
+
if (terms.length > 0 && config3.compressionLevel !== "ultra") {
|
|
22403
22661
|
filtered += `
|
|
22404
22662
|
Searchable terms: ${terms.join(", ")}
|
|
22405
22663
|
`;
|
|
22406
22664
|
}
|
|
22407
22665
|
filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
22408
|
-
output = filtered;
|
|
22666
|
+
output = compactLabel(filtered, config3.compressionLevel);
|
|
22409
22667
|
}
|
|
22410
22668
|
const responseBytes = Buffer.byteLength(output);
|
|
22411
22669
|
tracker.trackCall("execute", responseBytes);
|
|
@@ -22417,7 +22675,7 @@ Searchable terms: ${terms.join(", ")}
|
|
|
22417
22675
|
"Read a file and process it without loading contents into context. The file is read into a FILE_CONTENT variable inside the sandbox. Only your printed summary enters context.\n\nPREFER THIS OVER Read/cat for: log files, data files (CSV, JSON, XML), large source files for analysis, and any file where you need to extract specific information rather than read the entire content.",
|
|
22418
22676
|
{
|
|
22419
22677
|
path: external_exports.string().describe("Absolute file path or relative to project root"),
|
|
22420
|
-
language: external_exports.enum(
|
|
22678
|
+
language: external_exports.enum(LANGUAGE_ENUM).describe("Runtime language"),
|
|
22421
22679
|
code: external_exports.string().describe(
|
|
22422
22680
|
"Code to process FILE_CONTENT. Print summary via console.log/print/echo/IO.puts."
|
|
22423
22681
|
),
|
|
@@ -22425,7 +22683,17 @@ Searchable terms: ${terms.join(", ")}
|
|
|
22425
22683
|
timeout: external_exports.number().default(3e4).describe("Max execution time in ms")
|
|
22426
22684
|
},
|
|
22427
22685
|
async ({ path: filePath, language, code, intent, timeout }) => {
|
|
22428
|
-
const absPath = resolve(
|
|
22686
|
+
const absPath = resolve(projectDir, filePath);
|
|
22687
|
+
if (!isWithinProject(absPath)) {
|
|
22688
|
+
return {
|
|
22689
|
+
content: [
|
|
22690
|
+
{
|
|
22691
|
+
type: "text",
|
|
22692
|
+
text: `Error: path "${filePath}" is outside the project directory`
|
|
22693
|
+
}
|
|
22694
|
+
]
|
|
22695
|
+
};
|
|
22696
|
+
}
|
|
22429
22697
|
const result = await executor.executeFile({
|
|
22430
22698
|
language,
|
|
22431
22699
|
code,
|
|
@@ -22453,13 +22721,13 @@ ${result.stderr}`;
|
|
|
22453
22721
|
filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}
|
|
22454
22722
|
`;
|
|
22455
22723
|
}
|
|
22456
|
-
if (terms.length > 0) {
|
|
22724
|
+
if (terms.length > 0 && config3.compressionLevel !== "ultra") {
|
|
22457
22725
|
filtered += `
|
|
22458
22726
|
Searchable terms: ${terms.join(", ")}
|
|
22459
22727
|
`;
|
|
22460
22728
|
}
|
|
22461
22729
|
filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
22462
|
-
output = filtered;
|
|
22730
|
+
output = compactLabel(filtered, config3.compressionLevel);
|
|
22463
22731
|
}
|
|
22464
22732
|
const responseBytes = Buffer.byteLength(output);
|
|
22465
22733
|
tracker.trackCall("execute_file", responseBytes);
|
|
@@ -22478,7 +22746,28 @@ Searchable terms: ${terms.join(", ")}
|
|
|
22478
22746
|
let text;
|
|
22479
22747
|
let label = source ?? "indexed content";
|
|
22480
22748
|
if (filePath) {
|
|
22481
|
-
const absPath = resolve(
|
|
22749
|
+
const absPath = resolve(projectDir, filePath);
|
|
22750
|
+
if (!isWithinProject(absPath)) {
|
|
22751
|
+
return {
|
|
22752
|
+
content: [
|
|
22753
|
+
{
|
|
22754
|
+
type: "text",
|
|
22755
|
+
text: `Error: path "${filePath}" is outside the project directory`
|
|
22756
|
+
}
|
|
22757
|
+
]
|
|
22758
|
+
};
|
|
22759
|
+
}
|
|
22760
|
+
const fileStat = statSync(absPath);
|
|
22761
|
+
if (fileStat.size > 50 * 1024 * 1024) {
|
|
22762
|
+
return {
|
|
22763
|
+
content: [
|
|
22764
|
+
{
|
|
22765
|
+
type: "text",
|
|
22766
|
+
text: `Error: file "${filePath}" is too large (${(fileStat.size / 1024 / 1024).toFixed(1)}MB). Max 50MB.`
|
|
22767
|
+
}
|
|
22768
|
+
]
|
|
22769
|
+
};
|
|
22770
|
+
}
|
|
22482
22771
|
text = readFileSync2(absPath, "utf-8");
|
|
22483
22772
|
label = source ?? filePath;
|
|
22484
22773
|
} else if (content) {
|
|
@@ -22561,6 +22850,25 @@ ${hit.snippet}
|
|
|
22561
22850
|
source: external_exports.string().optional().describe("Label for the indexed content")
|
|
22562
22851
|
},
|
|
22563
22852
|
async ({ url, source }) => {
|
|
22853
|
+
try {
|
|
22854
|
+
const parsed = new URL(url);
|
|
22855
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
22856
|
+
return {
|
|
22857
|
+
content: [{ type: "text", text: "Error: only http/https URLs are allowed" }]
|
|
22858
|
+
};
|
|
22859
|
+
}
|
|
22860
|
+
if (isPrivateHost(parsed.hostname)) {
|
|
22861
|
+
return {
|
|
22862
|
+
content: [
|
|
22863
|
+
{ type: "text", text: "Error: internal/private URLs are not allowed" }
|
|
22864
|
+
]
|
|
22865
|
+
};
|
|
22866
|
+
}
|
|
22867
|
+
} catch {
|
|
22868
|
+
return {
|
|
22869
|
+
content: [{ type: "text", text: `Error: invalid URL "${url}"` }]
|
|
22870
|
+
};
|
|
22871
|
+
}
|
|
22564
22872
|
const label = source ?? url;
|
|
22565
22873
|
const fetchCode = buildFetchCode(url);
|
|
22566
22874
|
const result = await executor.execute({
|
|
@@ -22690,7 +22998,7 @@ Searchable terms: ${terms.join(", ")}`;
|
|
|
22690
22998
|
);
|
|
22691
22999
|
server2.tool(
|
|
22692
23000
|
"stats",
|
|
22693
|
-
"Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage,
|
|
23001
|
+
"Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage, context savings ratio, and visual charts.",
|
|
22694
23002
|
{},
|
|
22695
23003
|
async () => {
|
|
22696
23004
|
const report = tracker.formatReport();
|
|
@@ -22698,6 +23006,77 @@ Searchable terms: ${terms.join(", ")}`;
|
|
|
22698
23006
|
return { content: [{ type: "text", text: report }] };
|
|
22699
23007
|
}
|
|
22700
23008
|
);
|
|
23009
|
+
server2.tool(
|
|
23010
|
+
"discover",
|
|
23011
|
+
"Shows what's in the knowledge base and suggests optimization opportunities. Lists all indexed sources, chunk counts, searchable terms, and recommends next actions. Use this to understand what data is available for search.",
|
|
23012
|
+
{},
|
|
23013
|
+
async () => {
|
|
23014
|
+
const storeStats = store.getStats();
|
|
23015
|
+
const snap = tracker.getSnapshot();
|
|
23016
|
+
const lines = [];
|
|
23017
|
+
lines.push("## Knowledge Base Discovery\n");
|
|
23018
|
+
if (storeStats.totalSources === 0) {
|
|
23019
|
+
lines.push("No content indexed yet. Use these tools to build the knowledge base:\n");
|
|
23020
|
+
lines.push("- `batch_execute` \u2014 run commands and auto-index output");
|
|
23021
|
+
lines.push("- `execute` with `intent` \u2014 auto-indexes large output");
|
|
23022
|
+
lines.push("- `index` \u2014 index documentation or files");
|
|
23023
|
+
lines.push("- `fetch_and_index` \u2014 fetch and index web pages");
|
|
23024
|
+
} else {
|
|
23025
|
+
lines.push("| Metric | Value |");
|
|
23026
|
+
lines.push("|--------|-------|");
|
|
23027
|
+
lines.push(`| Indexed sources | ${storeStats.totalSources} |`);
|
|
23028
|
+
lines.push(`| Total chunks | ${storeStats.totalChunks} |`);
|
|
23029
|
+
lines.push(`| Vocabulary size | ${storeStats.vocabularySize} |`);
|
|
23030
|
+
lines.push(
|
|
23031
|
+
`| Trigram index | ${storeStats.hasTrigramTable ? "active" : "lazy (not yet needed)"} |`
|
|
23032
|
+
);
|
|
23033
|
+
const sources = store.listSources();
|
|
23034
|
+
if (sources.length > 0) {
|
|
23035
|
+
lines.push("\n### Indexed Sources\n");
|
|
23036
|
+
for (const src of sources) {
|
|
23037
|
+
lines.push(
|
|
23038
|
+
`- **${src.label}** \u2014 ${src.chunkCount} chunks${src.codeChunks > 0 ? ` (${src.codeChunks} with code)` : ""}`
|
|
23039
|
+
);
|
|
23040
|
+
}
|
|
23041
|
+
}
|
|
23042
|
+
const terms = store.getDistinctiveTerms();
|
|
23043
|
+
if (terms.length > 0) {
|
|
23044
|
+
lines.push("\n### Top Searchable Terms\n");
|
|
23045
|
+
lines.push(terms.slice(0, 20).join(", "));
|
|
23046
|
+
}
|
|
23047
|
+
}
|
|
23048
|
+
lines.push("\n### Optimization Suggestions\n");
|
|
23049
|
+
const totalCalls = Object.values(snap.calls).reduce((a, b) => a + b, 0);
|
|
23050
|
+
if (totalCalls === 0) {
|
|
23051
|
+
lines.push("- Start by using `batch_execute` to run multiple commands at once");
|
|
23052
|
+
} else {
|
|
23053
|
+
const searchCalls2 = snap.calls.search ?? 0;
|
|
23054
|
+
const executeCalls = snap.calls.execute ?? 0;
|
|
23055
|
+
const batchCalls = snap.calls.batch_execute ?? 0;
|
|
23056
|
+
if (executeCalls > 3 && batchCalls === 0) {
|
|
23057
|
+
lines.push(
|
|
23058
|
+
"- **Use batch_execute** \u2014 you've made multiple execute calls that could be batched into one"
|
|
23059
|
+
);
|
|
23060
|
+
}
|
|
23061
|
+
if (searchCalls2 > 5) {
|
|
23062
|
+
lines.push("- **Batch your searches** \u2014 pass multiple queries in a single search() call");
|
|
23063
|
+
}
|
|
23064
|
+
if (storeStats.totalChunks > 50) {
|
|
23065
|
+
lines.push(
|
|
23066
|
+
"- **Use source filtering** \u2014 scope searches with `source` parameter for faster, targeted results"
|
|
23067
|
+
);
|
|
23068
|
+
}
|
|
23069
|
+
if (storeStats.totalSources === 0 && totalCalls > 2) {
|
|
23070
|
+
lines.push(
|
|
23071
|
+
"- **Index more content** \u2014 use `intent` parameter in execute calls to auto-index large output"
|
|
23072
|
+
);
|
|
23073
|
+
}
|
|
23074
|
+
}
|
|
23075
|
+
const output = lines.join("\n");
|
|
23076
|
+
tracker.trackCall("discover", Buffer.byteLength(output));
|
|
23077
|
+
return { content: [{ type: "text", text: output }] };
|
|
23078
|
+
}
|
|
23079
|
+
);
|
|
22701
23080
|
return {
|
|
22702
23081
|
async start() {
|
|
22703
23082
|
const transport = new StdioServerTransport();
|