ctxloom-pro 1.0.29 → 1.0.31
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/apps/dashboard/package.json +1 -0
- package/bin/ctxloom.cjs +85 -0
- package/dist/index.js +17 -1
- package/package.json +3 -2
package/bin/ctxloom.cjs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ctxloom — CJS bootstrap that bumps the file-descriptor soft limit
|
|
7
|
+
* before loading the ESM entry point.
|
|
8
|
+
*
|
|
9
|
+
* Why this exists
|
|
10
|
+
* ───────────────
|
|
11
|
+
* Node.js does not expose `setrlimit(2)` natively, so we cannot raise
|
|
12
|
+
* RLIMIT_NOFILE from inside the JS runtime. macOS's launchctl default
|
|
13
|
+
* is `maxfiles = 256` (soft) / `unlimited` (hard), and every child
|
|
14
|
+
* process spawned by Claude.app / VS Code inherits that 256 soft cap.
|
|
15
|
+
*
|
|
16
|
+
* During a long-lived MCP session the cap is exhausted within ~20 tool
|
|
17
|
+
* calls — LanceDB keeps SSTable file handles open across queries, the
|
|
18
|
+
* ONNX runtime holds the model.onnx mmap, tree-sitter holds each WASM
|
|
19
|
+
* grammar, and the ~80 source files indexed at boot each leave a
|
|
20
|
+
* residual handle. The result is an EMFILE cascade that breaks every
|
|
21
|
+
* subsequent tool, including plain `fs.readFile`.
|
|
22
|
+
*
|
|
23
|
+
* Strategy
|
|
24
|
+
* ────────
|
|
25
|
+
* Re-exec ourselves through `/bin/sh -c "ulimit -n 65536; exec node …"`,
|
|
26
|
+
* gated by an env var to prevent an exec loop. On the second pass the
|
|
27
|
+
* raised limit is in place and we dynamic-import the real ESM entry.
|
|
28
|
+
*
|
|
29
|
+
* Windows is unaffected (the FD limit is much higher there, and `sh`
|
|
30
|
+
* is unavailable), so we skip the bump and load directly.
|
|
31
|
+
*
|
|
32
|
+
* Safe to call when the limit is already higher than our target — the
|
|
33
|
+
* second `ulimit` invocation is a no-op when the value is already at
|
|
34
|
+
* or above 65536.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const path = require('node:path');
|
|
38
|
+
|
|
39
|
+
const FD_LIMIT_TARGET = 65536;
|
|
40
|
+
const SENTINEL_ENV = 'CTXLOOM_FD_BUMPED';
|
|
41
|
+
const ENTRY = path.join(__dirname, '..', 'dist', 'index.js');
|
|
42
|
+
|
|
43
|
+
function shellQuote(arg) {
|
|
44
|
+
// POSIX-safe single-quote escape: a' becomes 'a'\'''.
|
|
45
|
+
return `'${String(arg).replace(/'/g, "'\\''")}'`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function shouldBump() {
|
|
49
|
+
if (process.env[SENTINEL_ENV] === '1') return false;
|
|
50
|
+
if (process.platform === 'win32') return false;
|
|
51
|
+
// Allow opt-out for unusual environments (e.g. setuid wrappers, CI
|
|
52
|
+
// runners that already manage rlimit themselves).
|
|
53
|
+
if (process.env['CTXLOOM_SKIP_FD_BUMP'] === '1') return false;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (shouldBump()) {
|
|
58
|
+
const { spawnSync } = require('node:child_process');
|
|
59
|
+
const quotedExec = shellQuote(process.execPath);
|
|
60
|
+
// process.argv[0] is the node path; argv[1] is THIS script; argv[2…] are
|
|
61
|
+
// the user's args. We want to re-exec node against the same script with
|
|
62
|
+
// the same user args, so we keep argv[1…] intact.
|
|
63
|
+
const quotedArgs = process.argv.slice(1).map(shellQuote).join(' ');
|
|
64
|
+
const cmd =
|
|
65
|
+
`ulimit -n ${FD_LIMIT_TARGET} 2>/dev/null; ` +
|
|
66
|
+
`${SENTINEL_ENV}=1 exec ${quotedExec} ${quotedArgs}`;
|
|
67
|
+
const result = spawnSync('/bin/sh', ['-c', cmd], { stdio: 'inherit' });
|
|
68
|
+
// spawnSync sets `status` on normal exit and `signal` on signal exit.
|
|
69
|
+
if (result.signal) {
|
|
70
|
+
process.kill(process.pid, result.signal);
|
|
71
|
+
// Fallback in case the signal is non-terminating in this context.
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
process.exit(result.status == null ? 1 : result.status);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// On the second pass (or on Windows) load the real ESM entry.
|
|
78
|
+
import(ENTRY).catch((err) => {
|
|
79
|
+
// Surface errors clearly — without this the user sees an unhandled
|
|
80
|
+
// promise rejection with no stack trace pointing back to the wrapper.
|
|
81
|
+
process.stderr.write(
|
|
82
|
+
`ctxloom: failed to load entry ${ENTRY}\n${err && err.stack ? err.stack : err}\n`,
|
|
83
|
+
);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -160,6 +160,22 @@ async function startServer(opts = {}) {
|
|
|
160
160
|
await server.connect(transport);
|
|
161
161
|
logger.info("MCP Server started on Stdio transport");
|
|
162
162
|
logger.info("Project root", { root: PROJECT_ROOT });
|
|
163
|
+
try {
|
|
164
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
165
|
+
const nofileSoft = Number(execSync2("ulimit -n", { shell: "/bin/sh" }).toString().trim());
|
|
166
|
+
if (Number.isFinite(nofileSoft)) {
|
|
167
|
+
const FD_WARN_THRESHOLD = 4096;
|
|
168
|
+
if (nofileSoft < FD_WARN_THRESHOLD) {
|
|
169
|
+
logger.warn(
|
|
170
|
+
"Low file-descriptor soft limit \u2014 EMFILE likely after ~20 tool calls. Run via `bin/ctxloom.cjs` (default bin) which bumps to 65536, or set `ulimit -n 65536` in your shell before launching.",
|
|
171
|
+
{ nofileSoft, threshold: FD_WARN_THRESHOLD }
|
|
172
|
+
);
|
|
173
|
+
} else {
|
|
174
|
+
logger.info("FD soft limit", { nofileSoft });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
163
179
|
Promise.all([ctx.getGraph(), generateEmbedding("warmup")]).then(async ([graph]) => {
|
|
164
180
|
logger.info("Ready", { edges: graph.edgeCount() });
|
|
165
181
|
if (withGit2) {
|
|
@@ -616,7 +632,7 @@ try {
|
|
|
616
632
|
} catch {
|
|
617
633
|
}
|
|
618
634
|
var args = process.argv.slice(2);
|
|
619
|
-
var ctxloomVersion = "1.0.
|
|
635
|
+
var ctxloomVersion = "1.0.31".length > 0 ? "1.0.31" : "dev";
|
|
620
636
|
if (args.includes("--version") || args.includes("-v")) {
|
|
621
637
|
process.stdout.write(`ctxloom ${ctxloomVersion}
|
|
622
638
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ctxloom-pro",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.31",
|
|
4
4
|
"description": "ctxloom — The Universal Code Context Engine. A local-first MCP server providing intelligent code context via hybrid Vector + AST + Graph search with Skeletonization (92% token reduction).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"bin": {
|
|
19
|
-
"ctxloom": "
|
|
19
|
+
"ctxloom": "bin/ctxloom.cjs"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
|
+
"bin/ctxloom.cjs",
|
|
22
23
|
"dist/**/*",
|
|
23
24
|
"!dist/**/*.map",
|
|
24
25
|
"apps/dashboard/dist/**/*",
|