context-compress 2026.3.3 → 2026.3.20
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 +14 -4
- 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 +7 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +148 -4
- package/dist/config.js.map +1 -1
- package/dist/executor.d.ts +11 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +104 -3
- package/dist/executor.js.map +1 -1
- package/dist/hooks/pretooluse.js +12 -6
- package/dist/hooks/pretooluse.js.map +1 -1
- package/dist/logger.d.ts +0 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +0 -6
- package/dist/logger.js.map +1 -1
- package/dist/network.d.ts +14 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +103 -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 +757 -243
- package/dist/server.bundle.mjs.map +4 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +262 -74
- package/dist/server.js.map +1 -1
- package/dist/stats.d.ts.map +1 -1
- package/dist/stats.js +43 -7
- package/dist/stats.js.map +1 -1
- package/dist/store.d.ts +20 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +78 -76
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +0 -19
- package/dist/types.d.ts.map +1 -1
- package/hooks/pretooluse.mjs +10 -5
- package/package.json +6 -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(projectDir2) {
|
|
6822
|
-
const paths = [
|
|
6823
|
-
projectDir2 && join(projectDir2, ".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(projectDir2) {
|
|
6859
|
-
if (_config) return _config;
|
|
6860
|
-
const fileConfig = loadFileConfig(projectDir2);
|
|
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,227 @@ 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
|
+
persistDb: false,
|
|
10866
|
+
dbDir: null
|
|
10867
|
+
};
|
|
10868
|
+
var LEVEL_OVERRIDES = {
|
|
10869
|
+
normal: {},
|
|
10870
|
+
compact: {
|
|
10871
|
+
maxOutputBytes: 51200,
|
|
10872
|
+
searchMaxBytes: 20480,
|
|
10873
|
+
batchMaxBytes: 40960,
|
|
10874
|
+
searchLimit: 2,
|
|
10875
|
+
intentSearchThreshold: 3e3
|
|
10876
|
+
},
|
|
10877
|
+
ultra: {
|
|
10878
|
+
maxOutputBytes: 25600,
|
|
10879
|
+
searchMaxBytes: 10240,
|
|
10880
|
+
batchMaxBytes: 20480,
|
|
10881
|
+
searchLimit: 1,
|
|
10882
|
+
intentSearchThreshold: 2e3
|
|
10883
|
+
}
|
|
10884
|
+
};
|
|
10885
|
+
var ConfigSchema = external_exports.object({
|
|
10886
|
+
passthroughEnvVars: external_exports.array(external_exports.string()).optional(),
|
|
10887
|
+
debug: external_exports.boolean().optional(),
|
|
10888
|
+
blockCurl: external_exports.boolean().optional(),
|
|
10889
|
+
blockWebFetch: external_exports.boolean().optional(),
|
|
10890
|
+
nudgeOnRead: external_exports.boolean().optional(),
|
|
10891
|
+
nudgeOnGrep: external_exports.boolean().optional(),
|
|
10892
|
+
intentSearchThreshold: external_exports.number().int().positive().optional(),
|
|
10893
|
+
maxOutputBytes: external_exports.number().int().positive().optional(),
|
|
10894
|
+
hardCapBytes: external_exports.number().int().positive().optional(),
|
|
10895
|
+
searchMaxBytes: external_exports.number().int().positive().optional(),
|
|
10896
|
+
batchMaxBytes: external_exports.number().int().positive().optional(),
|
|
10897
|
+
searchLimit: external_exports.number().int().positive().optional(),
|
|
10898
|
+
searchWindowMs: external_exports.number().int().positive().optional(),
|
|
10899
|
+
searchReduceAfter: external_exports.number().int().nonnegative().optional(),
|
|
10900
|
+
searchBlockAfter: external_exports.number().int().positive().optional(),
|
|
10901
|
+
compressionLevel: external_exports.enum(["normal", "compact", "ultra"]).optional(),
|
|
10902
|
+
persistDb: external_exports.boolean().optional(),
|
|
10903
|
+
dbDir: external_exports.string().nullable().optional()
|
|
10904
|
+
});
|
|
10905
|
+
function parseIntEnv(key) {
|
|
10906
|
+
const val = process.env[key];
|
|
10907
|
+
if (val === void 0) return void 0;
|
|
10908
|
+
const n = Number.parseInt(val, 10);
|
|
10909
|
+
return Number.isNaN(n) ? void 0 : n;
|
|
10910
|
+
}
|
|
10911
|
+
function loadFileConfig(projectDir2) {
|
|
10912
|
+
const paths = [
|
|
10913
|
+
projectDir2 && join(projectDir2, ".context-compress.json"),
|
|
10914
|
+
join(homedir(), ".context-compress.json")
|
|
10915
|
+
].filter(Boolean);
|
|
10916
|
+
for (const p of paths) {
|
|
10917
|
+
try {
|
|
10918
|
+
const raw = readFileSync(p, "utf-8");
|
|
10919
|
+
const parsed = JSON.parse(raw);
|
|
10920
|
+
const result = ConfigSchema.safeParse(parsed);
|
|
10921
|
+
if (result.success) {
|
|
10922
|
+
return result.data;
|
|
10923
|
+
}
|
|
10924
|
+
return {};
|
|
10925
|
+
} catch {
|
|
10926
|
+
}
|
|
10927
|
+
}
|
|
10928
|
+
return {};
|
|
10929
|
+
}
|
|
10930
|
+
function loadEnvConfig() {
|
|
10931
|
+
const partial2 = {};
|
|
10932
|
+
if (process.env.CONTEXT_COMPRESS_DEBUG === "1") {
|
|
10933
|
+
partial2.debug = true;
|
|
10934
|
+
}
|
|
10935
|
+
if (process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV) {
|
|
10936
|
+
partial2.passthroughEnvVars = process.env.CONTEXT_COMPRESS_PASSTHROUGH_ENV.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10937
|
+
}
|
|
10938
|
+
if (process.env.CONTEXT_COMPRESS_BLOCK_CURL !== void 0) {
|
|
10939
|
+
partial2.blockCurl = process.env.CONTEXT_COMPRESS_BLOCK_CURL !== "0";
|
|
10940
|
+
}
|
|
10941
|
+
if (process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== void 0) {
|
|
10942
|
+
partial2.blockWebFetch = process.env.CONTEXT_COMPRESS_BLOCK_WEBFETCH !== "0";
|
|
10943
|
+
}
|
|
10944
|
+
if (process.env.CONTEXT_COMPRESS_NUDGE_READ !== void 0) {
|
|
10945
|
+
partial2.nudgeOnRead = process.env.CONTEXT_COMPRESS_NUDGE_READ !== "0";
|
|
10946
|
+
}
|
|
10947
|
+
if (process.env.CONTEXT_COMPRESS_NUDGE_GREP !== void 0) {
|
|
10948
|
+
partial2.nudgeOnGrep = process.env.CONTEXT_COMPRESS_NUDGE_GREP !== "0";
|
|
10949
|
+
}
|
|
10950
|
+
const maxOutput = parseIntEnv("CONTEXT_COMPRESS_MAX_OUTPUT_BYTES");
|
|
10951
|
+
if (maxOutput !== void 0) partial2.maxOutputBytes = maxOutput;
|
|
10952
|
+
const hardCap = parseIntEnv("CONTEXT_COMPRESS_HARD_CAP_BYTES");
|
|
10953
|
+
if (hardCap !== void 0) partial2.hardCapBytes = hardCap;
|
|
10954
|
+
const searchMax = parseIntEnv("CONTEXT_COMPRESS_SEARCH_MAX_BYTES");
|
|
10955
|
+
if (searchMax !== void 0) partial2.searchMaxBytes = searchMax;
|
|
10956
|
+
const batchMax = parseIntEnv("CONTEXT_COMPRESS_BATCH_MAX_BYTES");
|
|
10957
|
+
if (batchMax !== void 0) partial2.batchMaxBytes = batchMax;
|
|
10958
|
+
const searchLimit = parseIntEnv("CONTEXT_COMPRESS_SEARCH_LIMIT");
|
|
10959
|
+
if (searchLimit !== void 0) partial2.searchLimit = searchLimit;
|
|
10960
|
+
const searchWindow = parseIntEnv("CONTEXT_COMPRESS_SEARCH_WINDOW_MS");
|
|
10961
|
+
if (searchWindow !== void 0) partial2.searchWindowMs = searchWindow;
|
|
10962
|
+
const searchReduce = parseIntEnv("CONTEXT_COMPRESS_SEARCH_REDUCE_AFTER");
|
|
10963
|
+
if (searchReduce !== void 0) partial2.searchReduceAfter = searchReduce;
|
|
10964
|
+
const searchBlock = parseIntEnv("CONTEXT_COMPRESS_SEARCH_BLOCK_AFTER");
|
|
10965
|
+
if (searchBlock !== void 0) partial2.searchBlockAfter = searchBlock;
|
|
10966
|
+
const intentThreshold = parseIntEnv("CONTEXT_COMPRESS_INTENT_SEARCH_THRESHOLD");
|
|
10967
|
+
if (intentThreshold !== void 0) partial2.intentSearchThreshold = intentThreshold;
|
|
10968
|
+
const level = process.env.CONTEXT_COMPRESS_LEVEL;
|
|
10969
|
+
if (level === "normal" || level === "compact" || level === "ultra") {
|
|
10970
|
+
partial2.compressionLevel = level;
|
|
10971
|
+
}
|
|
10972
|
+
if (process.env.CONTEXT_COMPRESS_PERSIST_DB === "1") {
|
|
10973
|
+
partial2.persistDb = true;
|
|
10974
|
+
}
|
|
10975
|
+
if (process.env.CONTEXT_COMPRESS_DB_DIR) {
|
|
10976
|
+
partial2.dbDir = process.env.CONTEXT_COMPRESS_DB_DIR;
|
|
10977
|
+
}
|
|
10978
|
+
return partial2;
|
|
10979
|
+
}
|
|
10980
|
+
var _config = null;
|
|
10981
|
+
function loadConfig(projectDir2) {
|
|
10982
|
+
if (_config) return _config;
|
|
10983
|
+
const fileConfig = loadFileConfig(projectDir2);
|
|
10984
|
+
const envConfig = loadEnvConfig();
|
|
10985
|
+
const merged = { ...DEFAULTS, ...fileConfig, ...envConfig };
|
|
10986
|
+
const levelOverrides = LEVEL_OVERRIDES[merged.compressionLevel];
|
|
10987
|
+
for (const [key, value] of Object.entries(levelOverrides)) {
|
|
10988
|
+
const k = key;
|
|
10989
|
+
if (!(k in fileConfig) && !(k in envConfig)) {
|
|
10990
|
+
merged[k] = value;
|
|
10991
|
+
}
|
|
10992
|
+
}
|
|
10993
|
+
if (merged.maxOutputBytes < 1024) {
|
|
10994
|
+
console.error(
|
|
10995
|
+
`[context-compress] Config: maxOutputBytes clamped from ${merged.maxOutputBytes} to 1024`
|
|
10996
|
+
);
|
|
10997
|
+
merged.maxOutputBytes = 1024;
|
|
10998
|
+
}
|
|
10999
|
+
if (merged.hardCapBytes < merged.maxOutputBytes) {
|
|
11000
|
+
console.error(
|
|
11001
|
+
`[context-compress] Config: hardCapBytes clamped from ${merged.hardCapBytes} to ${merged.maxOutputBytes}`
|
|
11002
|
+
);
|
|
11003
|
+
merged.hardCapBytes = merged.maxOutputBytes;
|
|
11004
|
+
}
|
|
11005
|
+
if (merged.intentSearchThreshold < 0) {
|
|
11006
|
+
console.error(
|
|
11007
|
+
`[context-compress] Config: intentSearchThreshold clamped from ${merged.intentSearchThreshold} to 0`
|
|
11008
|
+
);
|
|
11009
|
+
merged.intentSearchThreshold = 0;
|
|
11010
|
+
}
|
|
11011
|
+
if (merged.searchLimit < 1) {
|
|
11012
|
+
console.error(`[context-compress] Config: searchLimit clamped from ${merged.searchLimit} to 1`);
|
|
11013
|
+
merged.searchLimit = 1;
|
|
11014
|
+
}
|
|
11015
|
+
if (merged.searchWindowMs < 1e3) {
|
|
11016
|
+
console.error(
|
|
11017
|
+
`[context-compress] Config: searchWindowMs clamped from ${merged.searchWindowMs} to 1000`
|
|
11018
|
+
);
|
|
11019
|
+
merged.searchWindowMs = 1e3;
|
|
11020
|
+
}
|
|
11021
|
+
if (merged.searchReduceAfter < 1) {
|
|
11022
|
+
console.error(
|
|
11023
|
+
`[context-compress] Config: searchReduceAfter clamped from ${merged.searchReduceAfter} to 1`
|
|
11024
|
+
);
|
|
11025
|
+
merged.searchReduceAfter = 1;
|
|
11026
|
+
}
|
|
11027
|
+
if (merged.searchBlockAfter < merged.searchReduceAfter + 1) {
|
|
11028
|
+
const minVal = merged.searchReduceAfter + 1;
|
|
11029
|
+
console.error(
|
|
11030
|
+
`[context-compress] Config: searchBlockAfter clamped from ${merged.searchBlockAfter} to ${minVal}`
|
|
11031
|
+
);
|
|
11032
|
+
merged.searchBlockAfter = minVal;
|
|
11033
|
+
}
|
|
11034
|
+
if (merged.searchMaxBytes < 1024) {
|
|
11035
|
+
console.error(
|
|
11036
|
+
`[context-compress] Config: searchMaxBytes clamped from ${merged.searchMaxBytes} to 1024`
|
|
11037
|
+
);
|
|
11038
|
+
merged.searchMaxBytes = 1024;
|
|
11039
|
+
}
|
|
11040
|
+
if (merged.batchMaxBytes < 1024) {
|
|
11041
|
+
console.error(
|
|
11042
|
+
`[context-compress] Config: batchMaxBytes clamped from ${merged.batchMaxBytes} to 1024`
|
|
11043
|
+
);
|
|
11044
|
+
merged.batchMaxBytes = 1024;
|
|
11045
|
+
}
|
|
11046
|
+
if (merged.dbDir) merged.persistDb = true;
|
|
11047
|
+
_config = merged;
|
|
11048
|
+
return _config;
|
|
11049
|
+
}
|
|
11050
|
+
function getConfig() {
|
|
11051
|
+
if (!_config) return loadConfig();
|
|
11052
|
+
return _config;
|
|
11053
|
+
}
|
|
11054
|
+
|
|
11055
|
+
// src/logger.ts
|
|
11056
|
+
function debug(...args) {
|
|
11057
|
+
if (getConfig().debug) {
|
|
11058
|
+
process.stderr.write(`[context-compress] ${args.map(String).join(" ")}
|
|
11059
|
+
`);
|
|
11060
|
+
}
|
|
11061
|
+
}
|
|
11062
|
+
|
|
11063
|
+
// src/server.ts
|
|
11064
|
+
import { readFileSync as readFileSync2, realpathSync, statSync } from "node:fs";
|
|
11065
|
+
import { dirname, join as join4, resolve } from "node:path";
|
|
11066
|
+
import { fileURLToPath } from "node:url";
|
|
11067
|
+
|
|
10924
11068
|
// node_modules/zod/v4/core/core.js
|
|
10925
11069
|
var NEVER2 = Object.freeze({
|
|
10926
11070
|
status: "aborted"
|
|
@@ -21073,7 +21217,6 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
21073
21217
|
import { tmpdir } from "node:os";
|
|
21074
21218
|
import { join as join2 } from "node:path";
|
|
21075
21219
|
var DEFAULT_TIMEOUT = 3e4;
|
|
21076
|
-
var DEFAULT_HARD_CAP = 100 * 1024 * 1024;
|
|
21077
21220
|
var SAFE_ENV_KEYS = [
|
|
21078
21221
|
"PATH",
|
|
21079
21222
|
"HOME",
|
|
@@ -21126,6 +21269,86 @@ function killProcessTree(pid) {
|
|
|
21126
21269
|
}
|
|
21127
21270
|
}
|
|
21128
21271
|
}
|
|
21272
|
+
function deduplicateLines(output) {
|
|
21273
|
+
const lines = output.split("\n");
|
|
21274
|
+
if (lines.length < 3) return output;
|
|
21275
|
+
const result = [];
|
|
21276
|
+
let prevLine = lines[0];
|
|
21277
|
+
let count = 1;
|
|
21278
|
+
for (let i = 1; i < lines.length; i++) {
|
|
21279
|
+
if (lines[i] === prevLine && prevLine.trim().length > 0) {
|
|
21280
|
+
count++;
|
|
21281
|
+
} else {
|
|
21282
|
+
if (count > 2) {
|
|
21283
|
+
result.push(prevLine);
|
|
21284
|
+
result.push(` ... (\xD7${count} identical lines)`);
|
|
21285
|
+
} else {
|
|
21286
|
+
for (let j = 0; j < count; j++) result.push(prevLine);
|
|
21287
|
+
}
|
|
21288
|
+
prevLine = lines[i];
|
|
21289
|
+
count = 1;
|
|
21290
|
+
}
|
|
21291
|
+
}
|
|
21292
|
+
if (count > 2) {
|
|
21293
|
+
result.push(prevLine);
|
|
21294
|
+
result.push(` ... (\xD7${count} identical lines)`);
|
|
21295
|
+
} else {
|
|
21296
|
+
for (let j = 0; j < count; j++) result.push(prevLine);
|
|
21297
|
+
}
|
|
21298
|
+
return result.join("\n");
|
|
21299
|
+
}
|
|
21300
|
+
function groupErrorLines(output) {
|
|
21301
|
+
const lines = output.split("\n");
|
|
21302
|
+
if (lines.length < 5) return output;
|
|
21303
|
+
const ERROR_RE = /^(.*?(?:error|warning|Error|Warning|ERR|WARN)[:\s])\s*(.+?)(?:\s+(?:at|in|on)\s+(?:line\s+)?(\d+))?$/i;
|
|
21304
|
+
const errorGroups = /* @__PURE__ */ new Map();
|
|
21305
|
+
const resultLines = [];
|
|
21306
|
+
let groupedCount = 0;
|
|
21307
|
+
for (const line of lines) {
|
|
21308
|
+
const match = line.match(ERROR_RE);
|
|
21309
|
+
if (match) {
|
|
21310
|
+
const prefix = match[1].trim();
|
|
21311
|
+
const msg = match[2].trim();
|
|
21312
|
+
const key = `${prefix}|${msg}`;
|
|
21313
|
+
const existing = errorGroups.get(key);
|
|
21314
|
+
if (existing) {
|
|
21315
|
+
existing.count++;
|
|
21316
|
+
if (match[3]) existing.locations.push(match[3]);
|
|
21317
|
+
groupedCount++;
|
|
21318
|
+
continue;
|
|
21319
|
+
}
|
|
21320
|
+
errorGroups.set(key, {
|
|
21321
|
+
message: `${prefix} ${msg}`,
|
|
21322
|
+
locations: match[3] ? [match[3]] : [],
|
|
21323
|
+
count: 1
|
|
21324
|
+
});
|
|
21325
|
+
groupedCount++;
|
|
21326
|
+
continue;
|
|
21327
|
+
}
|
|
21328
|
+
resultLines.push(line);
|
|
21329
|
+
}
|
|
21330
|
+
if (groupedCount < 4 || errorGroups.size === groupedCount) return output;
|
|
21331
|
+
const grouped = [];
|
|
21332
|
+
for (const [, group] of errorGroups) {
|
|
21333
|
+
if (group.count === 1) {
|
|
21334
|
+
grouped.push(
|
|
21335
|
+
group.message + (group.locations.length ? ` at line ${group.locations[0]}` : "")
|
|
21336
|
+
);
|
|
21337
|
+
} else {
|
|
21338
|
+
let line = `${group.message} (\xD7${group.count})`;
|
|
21339
|
+
if (group.locations.length > 0) {
|
|
21340
|
+
line += ` [lines: ${group.locations.join(", ")}]`;
|
|
21341
|
+
}
|
|
21342
|
+
grouped.push(line);
|
|
21343
|
+
}
|
|
21344
|
+
}
|
|
21345
|
+
if (grouped.length > 0) {
|
|
21346
|
+
resultLines.push("");
|
|
21347
|
+
resultLines.push(`\u2500\u2500 Grouped errors/warnings (${groupedCount} \u2192 ${errorGroups.size}) \u2500\u2500`);
|
|
21348
|
+
resultLines.push(...grouped);
|
|
21349
|
+
}
|
|
21350
|
+
return resultLines.join("\n");
|
|
21351
|
+
}
|
|
21129
21352
|
function smartTruncate(output, maxBytes) {
|
|
21130
21353
|
if (Buffer.byteLength(output) <= maxBytes) return output;
|
|
21131
21354
|
const lines = output.split("\n");
|
|
@@ -21231,7 +21454,7 @@ var SubprocessExecutor = class {
|
|
|
21231
21454
|
plugin.needsShell
|
|
21232
21455
|
);
|
|
21233
21456
|
} finally {
|
|
21234
|
-
setTimeout(() => this.cleanupTempDir(tmpDir), 100);
|
|
21457
|
+
setTimeout(() => this.cleanupTempDir(tmpDir), 100).unref();
|
|
21235
21458
|
}
|
|
21236
21459
|
}
|
|
21237
21460
|
/**
|
|
@@ -21317,6 +21540,8 @@ var SubprocessExecutor = class {
|
|
|
21317
21540
|
stdout += `
|
|
21318
21541
|
[output capped at ${formatBytes(hardCap)} \u2014 process killed]`;
|
|
21319
21542
|
}
|
|
21543
|
+
stdout = deduplicateLines(stdout);
|
|
21544
|
+
stdout = groupErrorLines(stdout);
|
|
21320
21545
|
const truncated = Buffer.byteLength(stdout) > maxOutput;
|
|
21321
21546
|
if (truncated) {
|
|
21322
21547
|
stdout = smartTruncate(stdout, maxOutput);
|
|
@@ -21354,6 +21579,65 @@ async function __cm_main(){${code}}
|
|
|
21354
21579
|
__cm_main().then(()=>{${epilogue}}).catch(e=>{console.error(e);${epilogue}process.exit(1)});`;
|
|
21355
21580
|
}
|
|
21356
21581
|
|
|
21582
|
+
// src/network.ts
|
|
21583
|
+
import dns from "node:dns";
|
|
21584
|
+
function isPrivateHost(hostname2) {
|
|
21585
|
+
const h = hostname2.startsWith("[") && hostname2.endsWith("]") ? hostname2.slice(1, -1) : hostname2;
|
|
21586
|
+
const lower = h.toLowerCase();
|
|
21587
|
+
if (lower === "localhost" || lower === "0.0.0.0") return true;
|
|
21588
|
+
if (/^0\./.test(h)) return true;
|
|
21589
|
+
if (/^127\./.test(h)) return true;
|
|
21590
|
+
if (/^10\./.test(h)) return true;
|
|
21591
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
|
|
21592
|
+
if (/^192\.168\./.test(h)) return true;
|
|
21593
|
+
if (/^169\.254\./.test(h)) return true;
|
|
21594
|
+
if (/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(h)) return true;
|
|
21595
|
+
if (lower === "::1") return true;
|
|
21596
|
+
if (lower === "::" || lower === "0:0:0:0:0:0:0:0") return true;
|
|
21597
|
+
const mappedMatch = lower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
21598
|
+
if (mappedMatch) return isPrivateHost(mappedMatch[1]);
|
|
21599
|
+
if (/^fe[89ab]/i.test(h)) return true;
|
|
21600
|
+
if (/^f[cd]/i.test(h)) return true;
|
|
21601
|
+
return false;
|
|
21602
|
+
}
|
|
21603
|
+
async function resolveAndValidate(url) {
|
|
21604
|
+
const parsed = new URL(url);
|
|
21605
|
+
const hostname2 = parsed.hostname;
|
|
21606
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname2) || hostname2.includes(":")) {
|
|
21607
|
+
if (isPrivateHost(hostname2)) {
|
|
21608
|
+
throw new Error(`Blocked: resolved IP ${hostname2} is a private/internal address`);
|
|
21609
|
+
}
|
|
21610
|
+
return { url, resolvedIp: null };
|
|
21611
|
+
}
|
|
21612
|
+
let resolvedIp = null;
|
|
21613
|
+
let v4Error = false;
|
|
21614
|
+
let v6Error = false;
|
|
21615
|
+
try {
|
|
21616
|
+
const { address } = await dns.promises.lookup(hostname2, { family: 4 });
|
|
21617
|
+
if (isPrivateHost(address)) {
|
|
21618
|
+
throw new Error(`Blocked: ${hostname2} resolved to private IP ${address}`);
|
|
21619
|
+
}
|
|
21620
|
+
resolvedIp = address;
|
|
21621
|
+
} catch (err) {
|
|
21622
|
+
if (err instanceof Error && err.message.startsWith("Blocked:")) throw err;
|
|
21623
|
+
v4Error = true;
|
|
21624
|
+
}
|
|
21625
|
+
try {
|
|
21626
|
+
const { address } = await dns.promises.lookup(hostname2, { family: 6 });
|
|
21627
|
+
if (isPrivateHost(address)) {
|
|
21628
|
+
throw new Error(`Blocked: ${hostname2} resolved to private IPv6 ${address}`);
|
|
21629
|
+
}
|
|
21630
|
+
if (!resolvedIp) resolvedIp = address;
|
|
21631
|
+
} catch (err) {
|
|
21632
|
+
if (err instanceof Error && err.message.startsWith("Blocked:")) throw err;
|
|
21633
|
+
v6Error = true;
|
|
21634
|
+
}
|
|
21635
|
+
if (v4Error && v6Error) {
|
|
21636
|
+
throw new Error(`DNS resolution failed for ${hostname2}: unable to verify host safety`);
|
|
21637
|
+
}
|
|
21638
|
+
return { url, resolvedIp };
|
|
21639
|
+
}
|
|
21640
|
+
|
|
21357
21641
|
// src/runtime/index.ts
|
|
21358
21642
|
import { exec } from "node:child_process";
|
|
21359
21643
|
import { promisify } from "node:util";
|
|
@@ -21385,8 +21669,8 @@ var goPlugin = {
|
|
|
21385
21669
|
return [runtime, "run", filePath];
|
|
21386
21670
|
},
|
|
21387
21671
|
preprocessCode(code) {
|
|
21388
|
-
if (
|
|
21389
|
-
const hasImport =
|
|
21672
|
+
if (!/^package\s/m.test(code)) {
|
|
21673
|
+
const hasImport = /^import\s/m.test(code);
|
|
21390
21674
|
if (hasImport) {
|
|
21391
21675
|
return `package main
|
|
21392
21676
|
|
|
@@ -21405,7 +21689,7 @@ _ = fmt.Sprintf("")
|
|
|
21405
21689
|
},
|
|
21406
21690
|
wrapWithFileContent(code, filePath) {
|
|
21407
21691
|
const escaped = JSON.stringify(filePath);
|
|
21408
|
-
const hasPackage =
|
|
21692
|
+
const hasPackage = /^package\s/m.test(code);
|
|
21409
21693
|
if (hasPackage) {
|
|
21410
21694
|
return code.replace(
|
|
21411
21695
|
/(package\s+\w+\n)/,
|
|
@@ -21446,8 +21730,9 @@ var javascriptPlugin = {
|
|
|
21446
21730
|
},
|
|
21447
21731
|
wrapWithFileContent(code, filePath) {
|
|
21448
21732
|
const escaped = JSON.stringify(filePath);
|
|
21449
|
-
return `const
|
|
21450
|
-
const
|
|
21733
|
+
return `const {readFileSync: __cm_readFileSync} = await import("node:fs");
|
|
21734
|
+
const FILE_CONTENT_PATH = ${escaped};
|
|
21735
|
+
const FILE_CONTENT = __cm_readFileSync(FILE_CONTENT_PATH, "utf-8");
|
|
21451
21736
|
${code}`;
|
|
21452
21737
|
}
|
|
21453
21738
|
};
|
|
@@ -21519,7 +21804,7 @@ ${code}`;
|
|
|
21519
21804
|
// src/runtime/languages/r.ts
|
|
21520
21805
|
var rPlugin = {
|
|
21521
21806
|
language: "r",
|
|
21522
|
-
runtimeCandidates: ["Rscript", "
|
|
21807
|
+
runtimeCandidates: ["Rscript", "R"],
|
|
21523
21808
|
fileExtension: ".R",
|
|
21524
21809
|
buildCommand(runtime, filePath) {
|
|
21525
21810
|
return [runtime, filePath];
|
|
@@ -21562,7 +21847,7 @@ var rustPlugin = {
|
|
|
21562
21847
|
return [runtime, srcPath, "-o", binPath];
|
|
21563
21848
|
},
|
|
21564
21849
|
preprocessCode(code) {
|
|
21565
|
-
if (
|
|
21850
|
+
if (!/^fn\s+main\s*\(/m.test(code)) {
|
|
21566
21851
|
return `fn main() {
|
|
21567
21852
|
${code}
|
|
21568
21853
|
}`;
|
|
@@ -21575,7 +21860,7 @@ ${code}
|
|
|
21575
21860
|
let file_content_path = ${escaped};
|
|
21576
21861
|
let file_content = fs::read_to_string(file_content_path).unwrap();
|
|
21577
21862
|
`;
|
|
21578
|
-
if (
|
|
21863
|
+
if (/^fn\s+main\s*\(/m.test(code)) {
|
|
21579
21864
|
return code.replace(/fn main\s*\(\s*\)\s*\{/, `fn main() {
|
|
21580
21865
|
${preamble}`);
|
|
21581
21866
|
}
|
|
@@ -21611,8 +21896,9 @@ var typescriptPlugin = {
|
|
|
21611
21896
|
},
|
|
21612
21897
|
wrapWithFileContent(code, filePath) {
|
|
21613
21898
|
const escaped = JSON.stringify(filePath);
|
|
21614
|
-
return `const
|
|
21615
|
-
const
|
|
21899
|
+
return `const {readFileSync: __cm_readFileSync} = await import("node:fs");
|
|
21900
|
+
const FILE_CONTENT_PATH = ${escaped};
|
|
21901
|
+
const FILE_CONTENT = __cm_readFileSync(FILE_CONTENT_PATH, "utf-8");
|
|
21616
21902
|
${code}`;
|
|
21617
21903
|
},
|
|
21618
21904
|
// tsx and ts-node may be .cmd shims on Windows
|
|
@@ -21668,6 +21954,17 @@ function hasBun(runtimes) {
|
|
|
21668
21954
|
}
|
|
21669
21955
|
|
|
21670
21956
|
// src/stats.ts
|
|
21957
|
+
var BAR_WIDTH = 20;
|
|
21958
|
+
function asciiBar(ratio, width = BAR_WIDTH) {
|
|
21959
|
+
const filled = Math.round(ratio * width);
|
|
21960
|
+
const empty = width - filled;
|
|
21961
|
+
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}] ${(ratio * 100).toFixed(0)}%`;
|
|
21962
|
+
}
|
|
21963
|
+
function tokenCost(tokens) {
|
|
21964
|
+
const sonnetCost = tokens / 1e6 * 3;
|
|
21965
|
+
if (sonnetCost < 0.01) return "<$0.01";
|
|
21966
|
+
return `~$${sonnetCost.toFixed(2)} (Sonnet)`;
|
|
21967
|
+
}
|
|
21671
21968
|
var SessionTracker = class {
|
|
21672
21969
|
stats = {
|
|
21673
21970
|
calls: {},
|
|
@@ -21698,9 +21995,14 @@ var SessionTracker = class {
|
|
|
21698
21995
|
const totalReturned = Object.values(snap.bytesReturned).reduce((a, b) => a + b, 0);
|
|
21699
21996
|
const keptOut = snap.bytesIndexed + snap.bytesSandboxed;
|
|
21700
21997
|
const totalProcessed = keptOut + totalReturned;
|
|
21701
|
-
const savingsRatio = totalReturned > 0 ? totalProcessed / totalReturned : 1;
|
|
21998
|
+
const savingsRatio = totalReturned > 0 ? totalProcessed / totalReturned : keptOut > 0 ? Number.POSITIVE_INFINITY : 1;
|
|
21702
21999
|
const reductionPct = totalProcessed > 0 ? ((1 - totalReturned / totalProcessed) * 100).toFixed(1) : "0.0";
|
|
21703
|
-
const
|
|
22000
|
+
const estTokensLo = Math.round(totalReturned / 5);
|
|
22001
|
+
const estTokensHi = Math.round(totalReturned / 3);
|
|
22002
|
+
const estTokensAvoidedLo = Math.round(keptOut / 5);
|
|
22003
|
+
const estTokensAvoidedHi = Math.round(keptOut / 3);
|
|
22004
|
+
const estTokensMid = Math.round(totalReturned / 4);
|
|
22005
|
+
const estTokensAvoidedMid = Math.round(keptOut / 4);
|
|
21704
22006
|
const lines = [];
|
|
21705
22007
|
lines.push("## Session Statistics\n");
|
|
21706
22008
|
lines.push("| Metric | Value |");
|
|
@@ -21710,18 +22012,33 @@ var SessionTracker = class {
|
|
|
21710
22012
|
lines.push(`| Total data processed | ${formatBytes(totalProcessed)} |`);
|
|
21711
22013
|
lines.push(`| Kept in sandbox | ${formatBytes(keptOut)} |`);
|
|
21712
22014
|
lines.push(`| Context consumed | ${formatBytes(totalReturned)} |`);
|
|
21713
|
-
lines.push(`| Est. tokens | ~${estTokens.toLocaleString()} |`);
|
|
21714
22015
|
lines.push(
|
|
21715
|
-
`|
|
|
22016
|
+
`| Est. tokens used | ~${estTokensLo.toLocaleString()}-${estTokensHi.toLocaleString()} tokens (${tokenCost(estTokensMid)}) |`
|
|
22017
|
+
);
|
|
22018
|
+
lines.push(
|
|
22019
|
+
`| Est. tokens saved | ~${estTokensAvoidedLo.toLocaleString()}-${estTokensAvoidedHi.toLocaleString()} tokens (${tokenCost(estTokensAvoidedMid)}) |`
|
|
21716
22020
|
);
|
|
22021
|
+
const savingsLabel = Number.isFinite(savingsRatio) ? `${savingsRatio.toFixed(1)}x` : "\u221E";
|
|
22022
|
+
lines.push(`| **Savings ratio** | **${savingsLabel}** (${reductionPct}% reduction) |`);
|
|
22023
|
+
if (totalProcessed > 0) {
|
|
22024
|
+
const savingsBar = asciiBar(keptOut / totalProcessed);
|
|
22025
|
+
lines.push(`
|
|
22026
|
+
**Context savings:** ${savingsBar}`);
|
|
22027
|
+
lines.push(
|
|
22028
|
+
` Sandbox: ${formatBytes(keptOut)} kept out | Context: ${formatBytes(totalReturned)} entered`
|
|
22029
|
+
);
|
|
22030
|
+
}
|
|
21717
22031
|
if (totalCalls > 0) {
|
|
21718
22032
|
lines.push("\n## Per-Tool Breakdown\n");
|
|
21719
|
-
|
|
21720
|
-
lines.push("|------|-------|--------------|-------------|");
|
|
22033
|
+
const maxBytes = Math.max(...Object.values(snap.bytesReturned));
|
|
21721
22034
|
for (const [name, calls] of Object.entries(snap.calls)) {
|
|
21722
22035
|
const bytes = snap.bytesReturned[name] ?? 0;
|
|
22036
|
+
const tokLo = Math.round(bytes / 5);
|
|
22037
|
+
const tokHi = Math.round(bytes / 3);
|
|
22038
|
+
const barRatio = maxBytes > 0 ? bytes / maxBytes : 0;
|
|
22039
|
+
const bar = "\u2588".repeat(Math.max(1, Math.round(barRatio * 15)));
|
|
21723
22040
|
lines.push(
|
|
21724
|
-
|
|
22041
|
+
` ${name.padEnd(16)} ${String(calls).padStart(3)} calls ${bar} ${formatBytes(bytes)} (~${tokLo.toLocaleString()}-${tokHi.toLocaleString()} tok)`
|
|
21725
22042
|
);
|
|
21726
22043
|
}
|
|
21727
22044
|
}
|
|
@@ -21734,7 +22051,7 @@ Context-compress kept ${formatBytes(keptOut)} out of context (${reductionPct}% s
|
|
|
21734
22051
|
};
|
|
21735
22052
|
|
|
21736
22053
|
// src/store.ts
|
|
21737
|
-
import { readdirSync, unlinkSync } from "node:fs";
|
|
22054
|
+
import { mkdirSync as mkdirSync2, readdirSync, unlinkSync } from "node:fs";
|
|
21738
22055
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
21739
22056
|
import { join as join3 } from "node:path";
|
|
21740
22057
|
import Database from "better-sqlite3";
|
|
@@ -21825,20 +22142,15 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
21825
22142
|
"how",
|
|
21826
22143
|
"its",
|
|
21827
22144
|
"may",
|
|
21828
|
-
"new",
|
|
21829
22145
|
"now",
|
|
21830
22146
|
"old",
|
|
21831
22147
|
"see",
|
|
21832
22148
|
"way",
|
|
21833
22149
|
"who",
|
|
21834
22150
|
"did",
|
|
21835
|
-
"get",
|
|
21836
|
-
"got",
|
|
21837
|
-
"let",
|
|
21838
22151
|
"say",
|
|
21839
22152
|
"she",
|
|
21840
22153
|
"too",
|
|
21841
|
-
"use",
|
|
21842
22154
|
"will",
|
|
21843
22155
|
"with",
|
|
21844
22156
|
"this",
|
|
@@ -21852,7 +22164,6 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
21852
22164
|
"them",
|
|
21853
22165
|
"than",
|
|
21854
22166
|
"each",
|
|
21855
|
-
"make",
|
|
21856
22167
|
"like",
|
|
21857
22168
|
"just",
|
|
21858
22169
|
"over",
|
|
@@ -21892,21 +22203,7 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
21892
22203
|
"where",
|
|
21893
22204
|
"here",
|
|
21894
22205
|
"were",
|
|
21895
|
-
"much"
|
|
21896
|
-
"update",
|
|
21897
|
-
"updates",
|
|
21898
|
-
"updated",
|
|
21899
|
-
"deps",
|
|
21900
|
-
"dev",
|
|
21901
|
-
"tests",
|
|
21902
|
-
"test",
|
|
21903
|
-
"add",
|
|
21904
|
-
"added",
|
|
21905
|
-
"fix",
|
|
21906
|
-
"fixed",
|
|
21907
|
-
"run",
|
|
21908
|
-
"running",
|
|
21909
|
-
"using"
|
|
22206
|
+
"much"
|
|
21910
22207
|
]);
|
|
21911
22208
|
var HEADING_RE = /^(#{1,4})\s+(.+)$/;
|
|
21912
22209
|
var SEPARATOR_RE = /^[-_*]{3,}\s*$/;
|
|
@@ -21917,7 +22214,7 @@ var WORD_SPLIT_RE = /[^\p{L}\p{N}_-]+/u;
|
|
|
21917
22214
|
function sanitizeQuery(raw) {
|
|
21918
22215
|
const q = raw.replace(FTS_SPECIAL_RE, " ").replace(FTS_OPERATORS_RE, " ").trim();
|
|
21919
22216
|
const words = q.split(/\s+/).filter((w) => w.length >= 2).map((w) => `"${w}"`);
|
|
21920
|
-
return words.length > 0 ? words.join(" OR ") :
|
|
22217
|
+
return words.length > 0 ? words.join(" OR ") : "";
|
|
21921
22218
|
}
|
|
21922
22219
|
function levenshtein(a, b) {
|
|
21923
22220
|
if (a.length === 0) return b.length;
|
|
@@ -21937,8 +22234,23 @@ function levenshtein(a, b) {
|
|
|
21937
22234
|
var ContentStore = class {
|
|
21938
22235
|
db;
|
|
21939
22236
|
hasTrigramTable = false;
|
|
21940
|
-
constructor
|
|
21941
|
-
|
|
22237
|
+
// Cached prepared statements (initialized in initSchema, always available after constructor)
|
|
22238
|
+
insertSourceStmt;
|
|
22239
|
+
insertChunkStmt;
|
|
22240
|
+
vocabCountStmt;
|
|
22241
|
+
vocabInsertStmt;
|
|
22242
|
+
constructor(options) {
|
|
22243
|
+
let path;
|
|
22244
|
+
if (typeof options === "string") {
|
|
22245
|
+
path = options;
|
|
22246
|
+
} else if (options?.persistDb || options?.dbDir) {
|
|
22247
|
+
const dir = options.dbDir ?? join3(process.env.CLAUDE_PROJECT_DIR ?? process.cwd(), ".context-compress");
|
|
22248
|
+
mkdirSync2(dir, { recursive: true });
|
|
22249
|
+
path = join3(dir, "store.db");
|
|
22250
|
+
debug("Using persistent DB at", path);
|
|
22251
|
+
} else {
|
|
22252
|
+
path = (typeof options === "object" ? options?.dbPath : void 0) ?? join3(tmpdir2(), `context-compress-${process.pid}.db`);
|
|
22253
|
+
}
|
|
21942
22254
|
this.db = new Database(path);
|
|
21943
22255
|
this.db.pragma("journal_mode = WAL");
|
|
21944
22256
|
this.db.pragma("synchronous = NORMAL");
|
|
@@ -21966,6 +22278,14 @@ var ContentStore = class {
|
|
|
21966
22278
|
word TEXT PRIMARY KEY
|
|
21967
22279
|
);
|
|
21968
22280
|
`);
|
|
22281
|
+
this.insertSourceStmt = this.db.prepare(
|
|
22282
|
+
"INSERT INTO sources (label, chunk_count, code_chunk_count) VALUES (?, ?, ?)"
|
|
22283
|
+
);
|
|
22284
|
+
this.insertChunkStmt = this.db.prepare(
|
|
22285
|
+
"INSERT INTO chunks (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
|
|
22286
|
+
);
|
|
22287
|
+
this.vocabCountStmt = this.db.prepare("SELECT COUNT(*) as cnt FROM vocabulary");
|
|
22288
|
+
this.vocabInsertStmt = this.db.prepare("INSERT OR IGNORE INTO vocabulary (word) VALUES (?)");
|
|
21969
22289
|
}
|
|
21970
22290
|
/** Lazily create trigram table only when porter search returns 0 results */
|
|
21971
22291
|
ensureTrigramTable() {
|
|
@@ -21991,12 +22311,8 @@ var ContentStore = class {
|
|
|
21991
22311
|
index(content, label) {
|
|
21992
22312
|
const isMarkdown = HEADING_RE.test(content) || content.includes("```") || content.includes("---");
|
|
21993
22313
|
const chunks = isMarkdown ? chunkMarkdown(content) : chunkPlainText(content);
|
|
21994
|
-
const insertSource = this.
|
|
21995
|
-
|
|
21996
|
-
);
|
|
21997
|
-
const insertChunk = this.db.prepare(
|
|
21998
|
-
"INSERT INTO chunks (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
|
|
21999
|
-
);
|
|
22314
|
+
const insertSource = this.insertSourceStmt;
|
|
22315
|
+
const insertChunk = this.insertChunkStmt;
|
|
22000
22316
|
const insertTrigram = this.hasTrigramTable ? this.db.prepare(
|
|
22001
22317
|
"INSERT INTO chunks_trigram (title, content, source_id, content_type) VALUES (?, ?, ?, ?)"
|
|
22002
22318
|
) : null;
|
|
@@ -22028,6 +22344,9 @@ var ContentStore = class {
|
|
|
22028
22344
|
search(query, options) {
|
|
22029
22345
|
const limit = options?.limit ?? 3;
|
|
22030
22346
|
const sanitized = sanitizeQuery(query);
|
|
22347
|
+
if (!sanitized) {
|
|
22348
|
+
return { query, results: [] };
|
|
22349
|
+
}
|
|
22031
22350
|
let hits = this.porterSearch(sanitized, options?.source, limit);
|
|
22032
22351
|
if (hits.length > 0) {
|
|
22033
22352
|
return { query, results: hits };
|
|
@@ -22040,29 +22359,31 @@ var ContentStore = class {
|
|
|
22040
22359
|
const corrected = this.fuzzyCorrect(query);
|
|
22041
22360
|
if (corrected && corrected !== query) {
|
|
22042
22361
|
const correctedSanitized = sanitizeQuery(corrected);
|
|
22043
|
-
|
|
22044
|
-
|
|
22045
|
-
|
|
22362
|
+
if (correctedSanitized) {
|
|
22363
|
+
hits = this.porterSearch(correctedSanitized, options?.source, limit);
|
|
22364
|
+
if (hits.length > 0) {
|
|
22365
|
+
return { query, results: hits, corrected };
|
|
22366
|
+
}
|
|
22046
22367
|
}
|
|
22047
22368
|
}
|
|
22048
22369
|
return { query, results: [] };
|
|
22049
22370
|
}
|
|
22050
|
-
|
|
22371
|
+
ftsSearch(table, sanitized, source, limit) {
|
|
22051
22372
|
const sourceFilter = source ? "AND sources.label LIKE '%' || ? || '%'" : "";
|
|
22052
22373
|
const params = [sanitized];
|
|
22053
22374
|
if (source) params.push(source);
|
|
22054
22375
|
params.push(limit);
|
|
22055
22376
|
const sql = `
|
|
22056
22377
|
SELECT
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22378
|
+
${table}.title,
|
|
22379
|
+
${table}.content,
|
|
22380
|
+
${table}.content_type,
|
|
22060
22381
|
sources.label,
|
|
22061
|
-
bm25(
|
|
22062
|
-
highlight(
|
|
22063
|
-
FROM
|
|
22064
|
-
JOIN sources ON sources.id =
|
|
22065
|
-
WHERE
|
|
22382
|
+
bm25(${table}, 2.0, 1.0) AS rank,
|
|
22383
|
+
highlight(${table}, 1, char(2), char(3)) AS highlighted
|
|
22384
|
+
FROM ${table}
|
|
22385
|
+
JOIN sources ON sources.id = ${table}.source_id
|
|
22386
|
+
WHERE ${table} MATCH ? ${sourceFilter}
|
|
22066
22387
|
ORDER BY rank
|
|
22067
22388
|
LIMIT ?
|
|
22068
22389
|
`;
|
|
@@ -22075,41 +22396,15 @@ var ContentStore = class {
|
|
|
22075
22396
|
score: Math.abs(row.rank)
|
|
22076
22397
|
}));
|
|
22077
22398
|
} catch (e) {
|
|
22078
|
-
debug(
|
|
22399
|
+
debug(`FTS search error (${table}):`, e);
|
|
22079
22400
|
return [];
|
|
22080
22401
|
}
|
|
22081
22402
|
}
|
|
22403
|
+
porterSearch(sanitized, source, limit) {
|
|
22404
|
+
return this.ftsSearch("chunks", sanitized, source, limit);
|
|
22405
|
+
}
|
|
22082
22406
|
trigramSearch(sanitized, source, limit) {
|
|
22083
|
-
|
|
22084
|
-
const params = [sanitized];
|
|
22085
|
-
if (source) params.push(source);
|
|
22086
|
-
params.push(limit);
|
|
22087
|
-
const sql = `
|
|
22088
|
-
SELECT
|
|
22089
|
-
chunks_trigram.title,
|
|
22090
|
-
chunks_trigram.content,
|
|
22091
|
-
chunks_trigram.content_type,
|
|
22092
|
-
sources.label,
|
|
22093
|
-
bm25(chunks_trigram, 2.0, 1.0) AS rank,
|
|
22094
|
-
highlight(chunks_trigram, 1, char(2), char(3)) AS highlighted
|
|
22095
|
-
FROM chunks_trigram
|
|
22096
|
-
JOIN sources ON sources.id = chunks_trigram.source_id
|
|
22097
|
-
WHERE chunks_trigram MATCH ? ${sourceFilter}
|
|
22098
|
-
ORDER BY rank
|
|
22099
|
-
LIMIT ?
|
|
22100
|
-
`;
|
|
22101
|
-
try {
|
|
22102
|
-
const rows = this.db.prepare(sql).all(...params);
|
|
22103
|
-
return rows.map((row) => ({
|
|
22104
|
-
title: row.title,
|
|
22105
|
-
snippet: extractSnippet(row.highlighted),
|
|
22106
|
-
source: row.label,
|
|
22107
|
-
score: Math.abs(row.rank)
|
|
22108
|
-
}));
|
|
22109
|
-
} catch (e) {
|
|
22110
|
-
debug("Trigram search error:", e);
|
|
22111
|
-
return [];
|
|
22112
|
-
}
|
|
22407
|
+
return this.ftsSearch("chunks_trigram", sanitized, source, limit);
|
|
22113
22408
|
}
|
|
22114
22409
|
/**
|
|
22115
22410
|
* Fuzzy correction using vocabulary + Levenshtein distance.
|
|
@@ -22123,7 +22418,7 @@ var ContentStore = class {
|
|
|
22123
22418
|
const maxDist = word.length <= 4 ? 1 : word.length <= 12 ? 2 : 3;
|
|
22124
22419
|
const minLen = word.length - maxDist;
|
|
22125
22420
|
const maxLen = word.length + maxDist;
|
|
22126
|
-
const candidates = this.db.prepare("SELECT word FROM vocabulary WHERE length(word) BETWEEN ? AND ?").all(minLen, maxLen);
|
|
22421
|
+
const candidates = this.db.prepare("SELECT word FROM vocabulary WHERE length(word) BETWEEN ? AND ? LIMIT 500").all(minLen, maxLen);
|
|
22127
22422
|
let bestWord = word;
|
|
22128
22423
|
let bestDist = maxDist + 1;
|
|
22129
22424
|
for (const { word: candidate } of candidates) {
|
|
@@ -22142,11 +22437,11 @@ var ContentStore = class {
|
|
|
22142
22437
|
* Update vocabulary table from content (bounded to MAX_VOCABULARY).
|
|
22143
22438
|
*/
|
|
22144
22439
|
updateVocabulary(content) {
|
|
22145
|
-
const currentCount = this.
|
|
22440
|
+
const currentCount = this.vocabCountStmt.get().cnt;
|
|
22146
22441
|
if (currentCount >= MAX_VOCABULARY) return;
|
|
22147
22442
|
const words = content.split(WORD_SPLIT_RE).filter((w) => w.length >= 3 && !STOPWORDS.has(w.toLowerCase()));
|
|
22148
22443
|
const unique = new Set(words.map((w) => w.toLowerCase()));
|
|
22149
|
-
const insert = this.
|
|
22444
|
+
const insert = this.vocabInsertStmt;
|
|
22150
22445
|
let added = 0;
|
|
22151
22446
|
for (const word of unique) {
|
|
22152
22447
|
if (currentCount + added >= MAX_VOCABULARY) break;
|
|
@@ -22163,7 +22458,7 @@ var ContentStore = class {
|
|
|
22163
22458
|
).get(...sourceId ? [sourceId] : []).cnt;
|
|
22164
22459
|
if (totalChunks === 0) return [];
|
|
22165
22460
|
const filter = sourceId ? " WHERE source_id = ?" : "";
|
|
22166
|
-
const stmt = this.db.prepare(`SELECT content FROM chunks${filter}`);
|
|
22461
|
+
const stmt = this.db.prepare(`SELECT content FROM chunks${filter} LIMIT 500`);
|
|
22167
22462
|
const rows = sourceId ? stmt.all(sourceId) : stmt.all();
|
|
22168
22463
|
const docFreq = /* @__PURE__ */ new Map();
|
|
22169
22464
|
for (const row of rows) {
|
|
@@ -22188,6 +22483,21 @@ var ContentStore = class {
|
|
|
22188
22483
|
scored.sort((a, b) => b.score - a.score);
|
|
22189
22484
|
return scored.slice(0, 40).map((s) => s.word);
|
|
22190
22485
|
}
|
|
22486
|
+
/**
|
|
22487
|
+
* List all indexed sources with metadata.
|
|
22488
|
+
*/
|
|
22489
|
+
listSources() {
|
|
22490
|
+
const rows = this.db.prepare(
|
|
22491
|
+
"SELECT id, label, chunk_count, code_chunk_count, indexed_at FROM sources ORDER BY indexed_at DESC"
|
|
22492
|
+
).all();
|
|
22493
|
+
return rows.map((row) => ({
|
|
22494
|
+
id: row.id,
|
|
22495
|
+
label: row.label,
|
|
22496
|
+
chunkCount: row.chunk_count,
|
|
22497
|
+
codeChunks: row.code_chunk_count,
|
|
22498
|
+
indexedAt: row.indexed_at
|
|
22499
|
+
}));
|
|
22500
|
+
}
|
|
22191
22501
|
/**
|
|
22192
22502
|
* Get store statistics.
|
|
22193
22503
|
*/
|
|
@@ -22251,6 +22561,10 @@ function chunkMarkdown(content) {
|
|
|
22251
22561
|
}
|
|
22252
22562
|
currentLines.push(line);
|
|
22253
22563
|
}
|
|
22564
|
+
if (inFence) {
|
|
22565
|
+
debug("Warning: unclosed code fence detected during markdown chunking");
|
|
22566
|
+
hasCode = true;
|
|
22567
|
+
}
|
|
22254
22568
|
flush();
|
|
22255
22569
|
return chunks;
|
|
22256
22570
|
}
|
|
@@ -22264,7 +22578,7 @@ function chunkPlainText(content, linesPerChunk = 20, overlap = 2) {
|
|
|
22264
22578
|
return {
|
|
22265
22579
|
title: trimmed.split("\n")[0].slice(0, 80),
|
|
22266
22580
|
content: trimmed,
|
|
22267
|
-
hasCode:
|
|
22581
|
+
hasCode: /`{3,}/.test(trimmed)
|
|
22268
22582
|
};
|
|
22269
22583
|
}).filter(Boolean);
|
|
22270
22584
|
}
|
|
@@ -22313,8 +22627,8 @@ function cleanupStaleDbs() {
|
|
|
22313
22627
|
return cleaned;
|
|
22314
22628
|
}
|
|
22315
22629
|
|
|
22316
|
-
// src/
|
|
22317
|
-
var
|
|
22630
|
+
// src/types.ts
|
|
22631
|
+
var ALL_LANGUAGES = [
|
|
22318
22632
|
"javascript",
|
|
22319
22633
|
"typescript",
|
|
22320
22634
|
"python",
|
|
@@ -22327,10 +22641,19 @@ var LANGUAGES = [
|
|
|
22327
22641
|
"r",
|
|
22328
22642
|
"elixir"
|
|
22329
22643
|
];
|
|
22644
|
+
|
|
22645
|
+
// src/server.ts
|
|
22646
|
+
var LANGUAGE_ENUM = ALL_LANGUAGES;
|
|
22330
22647
|
var projectDir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
22331
22648
|
function isWithinProject(absPath) {
|
|
22332
|
-
|
|
22333
|
-
|
|
22649
|
+
try {
|
|
22650
|
+
const normalized = realpathSync(resolve(absPath));
|
|
22651
|
+
const realProjectDir = realpathSync(projectDir);
|
|
22652
|
+
return normalized === realProjectDir || normalized.startsWith(`${realProjectDir}/`);
|
|
22653
|
+
} catch {
|
|
22654
|
+
const normalized = resolve(absPath);
|
|
22655
|
+
return normalized === projectDir || normalized.startsWith(`${projectDir}/`);
|
|
22656
|
+
}
|
|
22334
22657
|
}
|
|
22335
22658
|
function getVersion() {
|
|
22336
22659
|
try {
|
|
@@ -22342,6 +22665,57 @@ function getVersion() {
|
|
|
22342
22665
|
return "1.0.0";
|
|
22343
22666
|
}
|
|
22344
22667
|
}
|
|
22668
|
+
function compactLabel(normal, level) {
|
|
22669
|
+
if (level === "ultra") {
|
|
22670
|
+
return normal.replace(/\*\*/g, "").replace(/Use search\(queries: \[\.\.\.]\) to retrieve.*$/gm, "\u2192 search() for more").replace(/Searchable terms: .+$/gm, "");
|
|
22671
|
+
}
|
|
22672
|
+
if (level === "compact") {
|
|
22673
|
+
return normal.replace(
|
|
22674
|
+
/Use search\(queries: \[\.\.\.]\) to retrieve full content of any section\./,
|
|
22675
|
+
"\u2192 search() for details"
|
|
22676
|
+
);
|
|
22677
|
+
}
|
|
22678
|
+
return normal;
|
|
22679
|
+
}
|
|
22680
|
+
async function limitConcurrency(tasks, limit) {
|
|
22681
|
+
const results = new Array(tasks.length);
|
|
22682
|
+
let nextIndex = 0;
|
|
22683
|
+
async function runNext() {
|
|
22684
|
+
while (nextIndex < tasks.length) {
|
|
22685
|
+
const index = nextIndex++;
|
|
22686
|
+
try {
|
|
22687
|
+
const value = await tasks[index]();
|
|
22688
|
+
results[index] = { status: "fulfilled", value };
|
|
22689
|
+
} catch (reason) {
|
|
22690
|
+
results[index] = { status: "rejected", reason };
|
|
22691
|
+
}
|
|
22692
|
+
}
|
|
22693
|
+
}
|
|
22694
|
+
const workers = Array.from({ length: Math.min(limit, tasks.length) }, () => runNext());
|
|
22695
|
+
await Promise.all(workers);
|
|
22696
|
+
return results;
|
|
22697
|
+
}
|
|
22698
|
+
function detectInjectionPatterns(content) {
|
|
22699
|
+
const warnings = [];
|
|
22700
|
+
const patterns = [
|
|
22701
|
+
{ re: /ignore\s+(all\s+)?previous\s+instructions/i, label: "instruction override" },
|
|
22702
|
+
{ re: /you\s+are\s+now\s+/i, label: "role reassignment" },
|
|
22703
|
+
{
|
|
22704
|
+
re: /(?:^|\n)\s*system\s*:\s*(?:you are|you're|as an? )/im,
|
|
22705
|
+
label: "system prompt injection"
|
|
22706
|
+
},
|
|
22707
|
+
{ re: /\[INST\]|\[\/INST\]|<\|im_start\|>|<\|im_end\|>/i, label: "chat template injection" },
|
|
22708
|
+
{ re: /\n\n(?:Human|Assistant):/m, label: "chat delimiter injection" },
|
|
22709
|
+
{ re: /reveal\s+(your|the)\s+(system|secret|confidential)/i, label: "data exfiltration" },
|
|
22710
|
+
{ re: /act\s+as\s+(if\s+you\s+are|a)\s+/i, label: "role manipulation" }
|
|
22711
|
+
];
|
|
22712
|
+
for (const { re, label } of patterns) {
|
|
22713
|
+
if (re.test(content)) {
|
|
22714
|
+
warnings.push(label);
|
|
22715
|
+
}
|
|
22716
|
+
}
|
|
22717
|
+
return warnings;
|
|
22718
|
+
}
|
|
22345
22719
|
async function createServer(config3) {
|
|
22346
22720
|
const version2 = getVersion();
|
|
22347
22721
|
debug("Version:", version2);
|
|
@@ -22350,8 +22724,48 @@ async function createServer(config3) {
|
|
|
22350
22724
|
const bunDetected = hasBun(runtimes);
|
|
22351
22725
|
debug("Runtimes detected:", runtimes.size);
|
|
22352
22726
|
const executor = new SubprocessExecutor(runtimes, config3);
|
|
22353
|
-
|
|
22727
|
+
let store;
|
|
22728
|
+
let dbFallback = false;
|
|
22729
|
+
try {
|
|
22730
|
+
store = new ContentStore({ persistDb: config3.persistDb, dbDir: config3.dbDir });
|
|
22731
|
+
} catch (e) {
|
|
22732
|
+
debug("Failed to create DB, falling back to in-memory:", e);
|
|
22733
|
+
store = new ContentStore(":memory:");
|
|
22734
|
+
dbFallback = true;
|
|
22735
|
+
}
|
|
22354
22736
|
const tracker = new SessionTracker();
|
|
22737
|
+
function applyIntentFilter(output, intent, sourceLabel) {
|
|
22738
|
+
if (Buffer.byteLength(output) <= config3.intentSearchThreshold) return output;
|
|
22739
|
+
const indexed = store.index(output, sourceLabel);
|
|
22740
|
+
tracker.trackIndexed(Buffer.byteLength(output));
|
|
22741
|
+
const searchResults = store.search(intent, { limit: 3 });
|
|
22742
|
+
const terms = store.getDistinctiveTerms(indexed.sourceId);
|
|
22743
|
+
let filtered = `Indexed ${indexed.totalChunks} sections from ${sourceLabel}.
|
|
22744
|
+
`;
|
|
22745
|
+
filtered += `${searchResults.results.length} sections matched "${intent}":
|
|
22746
|
+
|
|
22747
|
+
`;
|
|
22748
|
+
for (const hit of searchResults.results) {
|
|
22749
|
+
filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}
|
|
22750
|
+
`;
|
|
22751
|
+
}
|
|
22752
|
+
if (terms.length > 0 && config3.compressionLevel !== "ultra") {
|
|
22753
|
+
filtered += `
|
|
22754
|
+
Searchable terms: ${terms.join(", ")}
|
|
22755
|
+
`;
|
|
22756
|
+
}
|
|
22757
|
+
filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
22758
|
+
return compactLabel(filtered, config3.compressionLevel);
|
|
22759
|
+
}
|
|
22760
|
+
const shutdown = () => {
|
|
22761
|
+
try {
|
|
22762
|
+
store.close();
|
|
22763
|
+
} catch {
|
|
22764
|
+
}
|
|
22765
|
+
};
|
|
22766
|
+
process.on("SIGINT", shutdown);
|
|
22767
|
+
process.on("SIGTERM", shutdown);
|
|
22768
|
+
process.on("beforeExit", shutdown);
|
|
22355
22769
|
const searchCalls = [];
|
|
22356
22770
|
const server2 = new McpServer({
|
|
22357
22771
|
name: "context-compress",
|
|
@@ -22359,11 +22773,11 @@ async function createServer(config3) {
|
|
|
22359
22773
|
});
|
|
22360
22774
|
server2.tool(
|
|
22361
22775
|
"execute",
|
|
22362
|
-
`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
|
|
22776
|
+
`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 ~5KB. ${bunDetected ? "(Bun detected \u2014 JS/TS runs 3-5x faster) " : ""}Available: ${ALL_LANGUAGES.join(", ")}.
|
|
22363
22777
|
|
|
22364
22778
|
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.`,
|
|
22365
22779
|
{
|
|
22366
|
-
language: external_exports.enum(
|
|
22780
|
+
language: external_exports.enum(LANGUAGE_ENUM).describe("Runtime language"),
|
|
22367
22781
|
code: external_exports.string().describe(
|
|
22368
22782
|
"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."
|
|
22369
22783
|
),
|
|
@@ -22384,27 +22798,8 @@ PREFER THIS OVER BASH for: API calls (gh, curl, aws), test runners (npm test, py
|
|
|
22384
22798
|
STDERR:
|
|
22385
22799
|
${result.stderr}`;
|
|
22386
22800
|
}
|
|
22387
|
-
if (intent
|
|
22388
|
-
|
|
22389
|
-
tracker.trackIndexed(Buffer.byteLength(output));
|
|
22390
|
-
const searchResults = store.search(intent, { limit: 3 });
|
|
22391
|
-
const terms = store.getDistinctiveTerms(indexed.sourceId);
|
|
22392
|
-
let filtered = `Indexed ${indexed.totalChunks} sections from execute output.
|
|
22393
|
-
`;
|
|
22394
|
-
filtered += `${searchResults.results.length} sections matched "${intent}":
|
|
22395
|
-
|
|
22396
|
-
`;
|
|
22397
|
-
for (const hit of searchResults.results) {
|
|
22398
|
-
filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}
|
|
22399
|
-
`;
|
|
22400
|
-
}
|
|
22401
|
-
if (terms.length > 0) {
|
|
22402
|
-
filtered += `
|
|
22403
|
-
Searchable terms: ${terms.join(", ")}
|
|
22404
|
-
`;
|
|
22405
|
-
}
|
|
22406
|
-
filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
22407
|
-
output = filtered;
|
|
22801
|
+
if (intent) {
|
|
22802
|
+
output = applyIntentFilter(output, intent, `execute:${language}`);
|
|
22408
22803
|
}
|
|
22409
22804
|
const responseBytes = Buffer.byteLength(output);
|
|
22410
22805
|
tracker.trackCall("execute", responseBytes);
|
|
@@ -22416,7 +22811,7 @@ Searchable terms: ${terms.join(", ")}
|
|
|
22416
22811
|
"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.",
|
|
22417
22812
|
{
|
|
22418
22813
|
path: external_exports.string().describe("Absolute file path or relative to project root"),
|
|
22419
|
-
language: external_exports.enum(
|
|
22814
|
+
language: external_exports.enum(LANGUAGE_ENUM).describe("Runtime language"),
|
|
22420
22815
|
code: external_exports.string().describe(
|
|
22421
22816
|
"Code to process FILE_CONTENT. Print summary via console.log/print/echo/IO.puts."
|
|
22422
22817
|
),
|
|
@@ -22448,27 +22843,8 @@ Searchable terms: ${terms.join(", ")}
|
|
|
22448
22843
|
STDERR:
|
|
22449
22844
|
${result.stderr}`;
|
|
22450
22845
|
}
|
|
22451
|
-
if (intent
|
|
22452
|
-
|
|
22453
|
-
tracker.trackIndexed(Buffer.byteLength(output));
|
|
22454
|
-
const searchResults = store.search(intent, { limit: 3 });
|
|
22455
|
-
const terms = store.getDistinctiveTerms(indexed.sourceId);
|
|
22456
|
-
let filtered = `Indexed ${indexed.totalChunks} sections from "${filePath}" into knowledge base.
|
|
22457
|
-
`;
|
|
22458
|
-
filtered += `${searchResults.results.length} sections matched "${intent}":
|
|
22459
|
-
|
|
22460
|
-
`;
|
|
22461
|
-
for (const hit of searchResults.results) {
|
|
22462
|
-
filtered += ` - **${hit.title}**: ${hit.snippet.slice(0, 200)}
|
|
22463
|
-
`;
|
|
22464
|
-
}
|
|
22465
|
-
if (terms.length > 0) {
|
|
22466
|
-
filtered += `
|
|
22467
|
-
Searchable terms: ${terms.join(", ")}
|
|
22468
|
-
`;
|
|
22469
|
-
}
|
|
22470
|
-
filtered += "\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
22471
|
-
output = filtered;
|
|
22846
|
+
if (intent) {
|
|
22847
|
+
output = applyIntentFilter(output, intent, `file:${filePath}`);
|
|
22472
22848
|
}
|
|
22473
22849
|
const responseBytes = Buffer.byteLength(output);
|
|
22474
22850
|
tracker.trackCall("execute_file", responseBytes);
|
|
@@ -22498,9 +22874,38 @@ Searchable terms: ${terms.join(", ")}
|
|
|
22498
22874
|
]
|
|
22499
22875
|
};
|
|
22500
22876
|
}
|
|
22501
|
-
|
|
22502
|
-
|
|
22877
|
+
try {
|
|
22878
|
+
const fileStat = statSync(absPath);
|
|
22879
|
+
if (fileStat.size > 50 * 1024 * 1024) {
|
|
22880
|
+
return {
|
|
22881
|
+
content: [
|
|
22882
|
+
{
|
|
22883
|
+
type: "text",
|
|
22884
|
+
text: `Error: file "${filePath}" is too large (${(fileStat.size / 1024 / 1024).toFixed(1)}MB). Max 50MB.`
|
|
22885
|
+
}
|
|
22886
|
+
]
|
|
22887
|
+
};
|
|
22888
|
+
}
|
|
22889
|
+
text = readFileSync2(absPath, "utf-8");
|
|
22890
|
+
label = source ?? filePath;
|
|
22891
|
+
} catch (e) {
|
|
22892
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
22893
|
+
return {
|
|
22894
|
+
content: [{ type: "text", text: `Error reading "${filePath}": ${msg}` }]
|
|
22895
|
+
};
|
|
22896
|
+
}
|
|
22503
22897
|
} else if (content) {
|
|
22898
|
+
const contentBytes = Buffer.byteLength(content);
|
|
22899
|
+
if (contentBytes > 50 * 1024 * 1024) {
|
|
22900
|
+
return {
|
|
22901
|
+
content: [
|
|
22902
|
+
{
|
|
22903
|
+
type: "text",
|
|
22904
|
+
text: `Error: content too large (${(contentBytes / 1024 / 1024).toFixed(1)}MB). Max 50MB.`
|
|
22905
|
+
}
|
|
22906
|
+
]
|
|
22907
|
+
};
|
|
22908
|
+
}
|
|
22504
22909
|
text = content;
|
|
22505
22910
|
} else {
|
|
22506
22911
|
return {
|
|
@@ -22587,8 +22992,7 @@ ${hit.snippet}
|
|
|
22587
22992
|
content: [{ type: "text", text: "Error: only http/https URLs are allowed" }]
|
|
22588
22993
|
};
|
|
22589
22994
|
}
|
|
22590
|
-
|
|
22591
|
-
if (hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "::1" || hostname2 === "0.0.0.0" || hostname2.startsWith("10.") || hostname2.startsWith("172.16.") || hostname2.startsWith("192.168.") || hostname2.startsWith("169.254.")) {
|
|
22995
|
+
if (isPrivateHost(parsed.hostname)) {
|
|
22592
22996
|
return {
|
|
22593
22997
|
content: [
|
|
22594
22998
|
{ type: "text", text: "Error: internal/private URLs are not allowed" }
|
|
@@ -22600,8 +23004,22 @@ ${hit.snippet}
|
|
|
22600
23004
|
content: [{ type: "text", text: `Error: invalid URL "${url}"` }]
|
|
22601
23005
|
};
|
|
22602
23006
|
}
|
|
23007
|
+
let resolvedIp = null;
|
|
23008
|
+
try {
|
|
23009
|
+
const validated = await resolveAndValidate(url);
|
|
23010
|
+
resolvedIp = validated.resolvedIp;
|
|
23011
|
+
} catch (err) {
|
|
23012
|
+
return {
|
|
23013
|
+
content: [
|
|
23014
|
+
{
|
|
23015
|
+
type: "text",
|
|
23016
|
+
text: `Error: ${err instanceof Error ? err.message : "DNS validation failed"}`
|
|
23017
|
+
}
|
|
23018
|
+
]
|
|
23019
|
+
};
|
|
23020
|
+
}
|
|
22603
23021
|
const label = source ?? url;
|
|
22604
|
-
const fetchCode = buildFetchCode(url);
|
|
23022
|
+
const fetchCode = buildFetchCode(url, resolvedIp);
|
|
22605
23023
|
const result = await executor.execute({
|
|
22606
23024
|
language: "javascript",
|
|
22607
23025
|
code: fetchCode,
|
|
@@ -22614,6 +23032,7 @@ ${hit.snippet}
|
|
|
22614
23032
|
}
|
|
22615
23033
|
const markdown = result.stdout;
|
|
22616
23034
|
tracker.trackSandboxed(result.networkBytes ?? 0);
|
|
23035
|
+
const injectionWarnings = detectInjectionPatterns(markdown);
|
|
22617
23036
|
const indexed = store.index(markdown, label);
|
|
22618
23037
|
tracker.trackIndexed(Buffer.byteLength(markdown));
|
|
22619
23038
|
const preview = markdown.slice(0, 3072);
|
|
@@ -22630,6 +23049,11 @@ ${preview}`;
|
|
|
22630
23049
|
Searchable terms: ${terms.join(", ")}`;
|
|
22631
23050
|
}
|
|
22632
23051
|
output += "\n\nUse search(queries: [...]) to retrieve full content of any section.";
|
|
23052
|
+
if (injectionWarnings.length > 0) {
|
|
23053
|
+
output += `
|
|
23054
|
+
|
|
23055
|
+
\u26A0 Content safety notice: detected patterns (${injectionWarnings.join(", ")}). Review indexed content before relying on it.`;
|
|
23056
|
+
}
|
|
22633
23057
|
tracker.trackCall("fetch_and_index", Buffer.byteLength(output));
|
|
22634
23058
|
return { content: [{ type: "text", text: output }] };
|
|
22635
23059
|
}
|
|
@@ -22650,15 +23074,16 @@ Searchable terms: ${terms.join(", ")}`;
|
|
|
22650
23074
|
timeout: external_exports.number().default(6e4).describe("Max execution time in ms (default: 60s)")
|
|
22651
23075
|
},
|
|
22652
23076
|
async ({ commands, queries, timeout }) => {
|
|
22653
|
-
const commandResults = await
|
|
22654
|
-
commands.map(async (
|
|
23077
|
+
const commandResults = await limitConcurrency(
|
|
23078
|
+
commands.map((cmd) => async () => {
|
|
22655
23079
|
const result = await executor.execute({
|
|
22656
23080
|
language: "shell",
|
|
22657
23081
|
code: cmd.command,
|
|
22658
23082
|
timeout
|
|
22659
23083
|
});
|
|
22660
23084
|
return { label: cmd.label, result };
|
|
22661
|
-
})
|
|
23085
|
+
}),
|
|
23086
|
+
4
|
|
22662
23087
|
);
|
|
22663
23088
|
let combined = "";
|
|
22664
23089
|
const inventory = [];
|
|
@@ -22729,7 +23154,7 @@ Searchable terms: ${terms.join(", ")}`;
|
|
|
22729
23154
|
);
|
|
22730
23155
|
server2.tool(
|
|
22731
23156
|
"stats",
|
|
22732
|
-
"Returns context consumption statistics for the current session. Shows total bytes returned to context, breakdown by tool, call counts, estimated token usage,
|
|
23157
|
+
"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.",
|
|
22733
23158
|
{},
|
|
22734
23159
|
async () => {
|
|
22735
23160
|
const report = tracker.formatReport();
|
|
@@ -22737,6 +23162,82 @@ Searchable terms: ${terms.join(", ")}`;
|
|
|
22737
23162
|
return { content: [{ type: "text", text: report }] };
|
|
22738
23163
|
}
|
|
22739
23164
|
);
|
|
23165
|
+
server2.tool(
|
|
23166
|
+
"discover",
|
|
23167
|
+
"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.",
|
|
23168
|
+
{},
|
|
23169
|
+
async () => {
|
|
23170
|
+
const storeStats = store.getStats();
|
|
23171
|
+
const snap = tracker.getSnapshot();
|
|
23172
|
+
const lines = [];
|
|
23173
|
+
lines.push("## Knowledge Base Discovery\n");
|
|
23174
|
+
if (storeStats.totalSources === 0) {
|
|
23175
|
+
lines.push("No content indexed yet. Use these tools to build the knowledge base:\n");
|
|
23176
|
+
lines.push("- `batch_execute` \u2014 run commands and auto-index output");
|
|
23177
|
+
lines.push("- `execute` with `intent` \u2014 auto-indexes large output");
|
|
23178
|
+
lines.push("- `index` \u2014 index documentation or files");
|
|
23179
|
+
lines.push("- `fetch_and_index` \u2014 fetch and index web pages");
|
|
23180
|
+
} else {
|
|
23181
|
+
lines.push("| Metric | Value |");
|
|
23182
|
+
lines.push("|--------|-------|");
|
|
23183
|
+
lines.push(`| Indexed sources | ${storeStats.totalSources} |`);
|
|
23184
|
+
lines.push(`| Total chunks | ${storeStats.totalChunks} |`);
|
|
23185
|
+
lines.push(`| Vocabulary size | ${storeStats.vocabularySize} |`);
|
|
23186
|
+
lines.push(
|
|
23187
|
+
`| Trigram index | ${storeStats.hasTrigramTable ? "active" : "lazy (not yet needed)"} |`
|
|
23188
|
+
);
|
|
23189
|
+
const sources = store.listSources();
|
|
23190
|
+
if (sources.length > 0) {
|
|
23191
|
+
lines.push("\n### Indexed Sources\n");
|
|
23192
|
+
for (const src of sources) {
|
|
23193
|
+
lines.push(
|
|
23194
|
+
`- **${src.label}** \u2014 ${src.chunkCount} chunks${src.codeChunks > 0 ? ` (${src.codeChunks} with code)` : ""}`
|
|
23195
|
+
);
|
|
23196
|
+
}
|
|
23197
|
+
}
|
|
23198
|
+
const terms = store.getDistinctiveTerms();
|
|
23199
|
+
if (terms.length > 0) {
|
|
23200
|
+
lines.push("\n### Top Searchable Terms\n");
|
|
23201
|
+
lines.push(terms.slice(0, 20).join(", "));
|
|
23202
|
+
}
|
|
23203
|
+
}
|
|
23204
|
+
lines.push("\n### Optimization Suggestions\n");
|
|
23205
|
+
const totalCalls = Object.values(snap.calls).reduce((a, b) => a + b, 0);
|
|
23206
|
+
if (totalCalls === 0) {
|
|
23207
|
+
lines.push("- Start by using `batch_execute` to run multiple commands at once");
|
|
23208
|
+
} else {
|
|
23209
|
+
const searchCalls2 = snap.calls.search ?? 0;
|
|
23210
|
+
const executeCalls = snap.calls.execute ?? 0;
|
|
23211
|
+
const batchCalls = snap.calls.batch_execute ?? 0;
|
|
23212
|
+
if (executeCalls > 3 && batchCalls === 0) {
|
|
23213
|
+
lines.push(
|
|
23214
|
+
"- **Use batch_execute** \u2014 you've made multiple execute calls that could be batched into one"
|
|
23215
|
+
);
|
|
23216
|
+
}
|
|
23217
|
+
if (searchCalls2 > 5) {
|
|
23218
|
+
lines.push("- **Batch your searches** \u2014 pass multiple queries in a single search() call");
|
|
23219
|
+
}
|
|
23220
|
+
if (storeStats.totalChunks > 50) {
|
|
23221
|
+
lines.push(
|
|
23222
|
+
"- **Use source filtering** \u2014 scope searches with `source` parameter for faster, targeted results"
|
|
23223
|
+
);
|
|
23224
|
+
}
|
|
23225
|
+
if (storeStats.totalSources === 0 && totalCalls > 2) {
|
|
23226
|
+
lines.push(
|
|
23227
|
+
"- **Index more content** \u2014 use `intent` parameter in execute calls to auto-index large output"
|
|
23228
|
+
);
|
|
23229
|
+
}
|
|
23230
|
+
}
|
|
23231
|
+
if (dbFallback) {
|
|
23232
|
+
lines.push(
|
|
23233
|
+
"\n\u26A0 **Warning:** Persistent DB creation failed \u2014 using in-memory storage. Indexed data will not survive restarts."
|
|
23234
|
+
);
|
|
23235
|
+
}
|
|
23236
|
+
const output = lines.join("\n");
|
|
23237
|
+
tracker.trackCall("discover", Buffer.byteLength(output));
|
|
23238
|
+
return { content: [{ type: "text", text: output }] };
|
|
23239
|
+
}
|
|
23240
|
+
);
|
|
22740
23241
|
return {
|
|
22741
23242
|
async start() {
|
|
22742
23243
|
const transport = new StdioServerTransport();
|
|
@@ -22745,11 +23246,21 @@ Searchable terms: ${terms.join(", ")}`;
|
|
|
22745
23246
|
}
|
|
22746
23247
|
};
|
|
22747
23248
|
}
|
|
22748
|
-
function buildFetchCode(url) {
|
|
22749
|
-
|
|
22750
|
-
|
|
22751
|
-
const
|
|
22752
|
-
const
|
|
23249
|
+
function buildFetchCode(url, resolvedIp) {
|
|
23250
|
+
let fetchSetup;
|
|
23251
|
+
if (resolvedIp) {
|
|
23252
|
+
const pinnedUrl = new URL(url);
|
|
23253
|
+
const originalHost = pinnedUrl.host;
|
|
23254
|
+
pinnedUrl.hostname = resolvedIp;
|
|
23255
|
+
fetchSetup = `
|
|
23256
|
+
const url = ${JSON.stringify(pinnedUrl.toString())};
|
|
23257
|
+
const resp = await fetch(url, { headers: { 'Host': ${JSON.stringify(originalHost)} }, redirect: 'error' });`;
|
|
23258
|
+
} else {
|
|
23259
|
+
fetchSetup = `
|
|
23260
|
+
const url = ${JSON.stringify(url)};
|
|
23261
|
+
const resp = await fetch(url, { redirect: 'error' });`;
|
|
23262
|
+
}
|
|
23263
|
+
return `${fetchSetup}
|
|
22753
23264
|
if (!resp.ok) { console.error("HTTP " + resp.status); process.exit(1); }
|
|
22754
23265
|
const html = await resp.text();
|
|
22755
23266
|
|
|
@@ -22785,12 +23296,15 @@ md = md.replace(/<br\\s*\\/?>/gi, "\\n");
|
|
|
22785
23296
|
md = md.replace(/<[^>]+>/g, "");
|
|
22786
23297
|
|
|
22787
23298
|
// Decode entities
|
|
22788
|
-
md = md.replace(/&
|
|
22789
|
-
.replace(/</g, "<")
|
|
23299
|
+
md = md.replace(/</g, "<")
|
|
22790
23300
|
.replace(/>/g, ">")
|
|
22791
23301
|
.replace(/"/g, '"')
|
|
22792
23302
|
.replace(/'/g, "'")
|
|
22793
|
-
.replace(/&
|
|
23303
|
+
.replace(/'/g, "'")
|
|
23304
|
+
.replace(/ /g, " ")
|
|
23305
|
+
.replace(/&#(\\d+);/g, (_, n) => { const c = parseInt(n, 10); return c > 0 && c <= 0x10FFFF ? String.fromCodePoint(c) : ''; })
|
|
23306
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => { const c = parseInt(h, 16); return c > 0 && c <= 0x10FFFF ? String.fromCodePoint(c) : ''; })
|
|
23307
|
+
.replace(/&/g, "&");
|
|
22794
23308
|
|
|
22795
23309
|
// Clean whitespace
|
|
22796
23310
|
md = md.replace(/\\n{3,}/g, "\\n\\n").trim();
|