context-mode 1.0.20 → 1.0.21
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/build/server.js +1 -1
- package/cli.bundle.mjs +1 -1
- package/hooks/core/routing.mjs +46 -6
- package/package.json +1 -1
- package/server.bundle.mjs +1 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.21"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "context-mode",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
16
|
-
"version": "1.0.
|
|
16
|
+
"version": "1.0.21",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Mert Koseoğlu"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
package/build/server.js
CHANGED
|
@@ -14,7 +14,7 @@ import { readBashPolicies, evaluateCommandDenyOnly, extractShellCommands, readTo
|
|
|
14
14
|
import { detectRuntimes, getRuntimeSummary, getAvailableLanguages, hasBunRuntime, } from "./runtime.js";
|
|
15
15
|
import { classifyNonZeroExit } from "./exit-classify.js";
|
|
16
16
|
import { startLifecycleGuard } from "./lifecycle.js";
|
|
17
|
-
const VERSION = "1.0.
|
|
17
|
+
const VERSION = "1.0.21";
|
|
18
18
|
// Prevent silent server death from unhandled async errors
|
|
19
19
|
process.on("unhandledRejection", (err) => {
|
|
20
20
|
process.stderr.write(`[context-mode] unhandledRejection: ${err}\n`);
|
package/cli.bundle.mjs
CHANGED
|
@@ -293,7 +293,7 @@ async function main() {
|
|
|
293
293
|
main();
|
|
294
294
|
`}async function sN(){let t=S0();t>0&&console.error(`Cleaned up ${t} stale DB file(s) from previous sessions`);let e=()=>{Ds.cleanupBackgrounded(),mo&&mo.cleanup()},r=async()=>{e(),process.exit(0)};process.on("exit",e),process.on("SIGINT",()=>{r()}),process.on("SIGTERM",()=>{r()}),N0({onShutdown:()=>r()});let n=new lc;await Qt.connect(n);try{let{detectPlatform:o,getAdapter:s}=await Promise.resolve().then(()=>(eu(),jh)),i=o(),a=await s(i.platform);if(!a.capabilities.sessionStart){let c=Bz(Ms(Am(import.meta.url)),".."),u=process.env.CLAUDE_PROJECT_DIR??process.env.CODEX_HOME??process.cwd(),l=a.writeRoutingInstructions(u,c);l&&console.error(`Wrote routing instructions: ${l}`)}}catch{}console.error(`Context Mode MCP server v${L0} running on stdio`),console.error(`Detected runtimes:
|
|
295
295
|
${Js(jm)}`),_o()||(console.error(`
|
|
296
|
-
Performance tip: Install Bun for 3-5x faster JS/TS execution`),console.error(" curl -fsSL https://bun.sh/install | bash"))}var L0,jm,Kz,Qt,Ds,mo,Ye,Yz,Xz,Qz,eN,mc,$n,Om,tN,j0,M0,Cm,zm,H0=b(()=>{"use strict";o0();c0();$m();wm();k0();C0();Xs();z0();A0();L0="1.0.
|
|
296
|
+
Performance tip: Install Bun for 3-5x faster JS/TS execution`),console.error(" curl -fsSL https://bun.sh/install | bash"))}var L0,jm,Kz,Qt,Ds,mo,Ye,Yz,Xz,Qz,eN,mc,$n,Om,tN,j0,M0,Cm,zm,H0=b(()=>{"use strict";o0();c0();$m();wm();k0();C0();Xs();z0();A0();L0="1.0.20";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
|
|
297
297
|
`)});process.on("uncaughtException",t=>{process.stderr.write(`[context-mode] uncaughtException: ${t?.message??t}
|
|
298
298
|
`)});jm=wn(),Kz=Ys(jm),Qt=new cc({name:"context-mode",version:L0}),Ds=new js({runtimes:jm,projectRoot:process.env.CLAUDE_PROJECT_DIR}),mo=null;Ye={calls:{},bytesReturned:{},bytesIndexed:0,bytesSandboxed:0,sessionStart:Date.now()};Yz=Kz.join(", "),Xz=_o()?" (Bun detected \u2014 JS/TS runs 3-5x faster)":"",Qz="",eN="";Qt.registerTool("ctx_execute",{title:"Execute Code",description:`MANDATORY: Use for any command where output exceeds 20 lines. Execute code in a sandboxed subprocess. Only stdout enters context \u2014 raw data stays in the subprocess.${Xz} Available: ${Yz}.
|
|
299
299
|
|
package/hooks/core/routing.mjs
CHANGED
|
@@ -11,6 +11,46 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { ROUTING_BLOCK, READ_GUIDANCE, GREP_GUIDANCE, BASH_GUIDANCE } from "../routing-block.mjs";
|
|
14
|
+
import { existsSync, mkdirSync, rmSync, openSync, closeSync, constants as fsConstants } from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { resolve } from "node:path";
|
|
17
|
+
|
|
18
|
+
// Guidance throttle: show each advisory type at most once per session.
|
|
19
|
+
// Hybrid approach:
|
|
20
|
+
// - In-memory Set for same-process (OpenCode ts-plugin, vitest)
|
|
21
|
+
// - File-based markers with O_EXCL for cross-process atomicity
|
|
22
|
+
// (Claude Code, Gemini, Cursor, VS Code Copilot)
|
|
23
|
+
// Session scoped via process.ppid (= host PID, constant for session lifetime).
|
|
24
|
+
const _guidanceShown = new Set();
|
|
25
|
+
const _guidanceDir = resolve(tmpdir(), `context-mode-guidance-${process.ppid}`);
|
|
26
|
+
|
|
27
|
+
function guidanceOnce(type, content) {
|
|
28
|
+
// Fast path: in-memory (same process)
|
|
29
|
+
if (_guidanceShown.has(type)) return null;
|
|
30
|
+
|
|
31
|
+
// Ensure marker directory exists
|
|
32
|
+
try { mkdirSync(_guidanceDir, { recursive: true }); } catch {}
|
|
33
|
+
|
|
34
|
+
// Atomic create-or-fail: O_CREAT | O_EXCL | O_WRONLY
|
|
35
|
+
// First process to create the file wins; others get EEXIST.
|
|
36
|
+
const marker = resolve(_guidanceDir, type);
|
|
37
|
+
try {
|
|
38
|
+
const fd = openSync(marker, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY);
|
|
39
|
+
closeSync(fd);
|
|
40
|
+
} catch {
|
|
41
|
+
// EEXIST = another process already created it, or we did in-memory
|
|
42
|
+
_guidanceShown.add(type);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
_guidanceShown.add(type);
|
|
47
|
+
return { action: "context", additionalContext: content };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resetGuidanceThrottle() {
|
|
51
|
+
_guidanceShown.clear();
|
|
52
|
+
try { rmSync(_guidanceDir, { recursive: true, force: true }); } catch {}
|
|
53
|
+
}
|
|
14
54
|
|
|
15
55
|
/**
|
|
16
56
|
* Strip heredoc content from a shell command.
|
|
@@ -157,18 +197,18 @@ export function routePreToolUse(toolName, toolInput, projectDir) {
|
|
|
157
197
|
};
|
|
158
198
|
}
|
|
159
199
|
|
|
160
|
-
// allow all other Bash commands, but inject routing nudge
|
|
161
|
-
return
|
|
200
|
+
// allow all other Bash commands, but inject routing nudge (once per session)
|
|
201
|
+
return guidanceOnce("bash", BASH_GUIDANCE);
|
|
162
202
|
}
|
|
163
203
|
|
|
164
|
-
// ─── Read: nudge toward execute_file ───
|
|
204
|
+
// ─── Read: nudge toward execute_file (once per session) ───
|
|
165
205
|
if (canonical === "Read") {
|
|
166
|
-
return
|
|
206
|
+
return guidanceOnce("read", READ_GUIDANCE);
|
|
167
207
|
}
|
|
168
208
|
|
|
169
|
-
// ─── Grep: nudge toward execute ───
|
|
209
|
+
// ─── Grep: nudge toward execute (once per session) ───
|
|
170
210
|
if (canonical === "Grep") {
|
|
171
|
-
return
|
|
211
|
+
return guidanceOnce("grep", GREP_GUIDANCE);
|
|
172
212
|
}
|
|
173
213
|
|
|
174
214
|
// ─── WebFetch: deny + redirect to sandbox ───
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP plugin that saves 98% of your context window. Works with Claude Code, Gemini CLI, VS Code Copilot, OpenCode, and Codex CLI. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|
package/server.bundle.mjs
CHANGED
|
@@ -238,7 +238,7 @@ stdout:
|
|
|
238
238
|
${n}
|
|
239
239
|
|
|
240
240
|
stderr:
|
|
241
|
-
${o}`}}var rO=process.ppid;function nO(){let t=process.ppid;return!(t!==rO||t===0||t===1)}function Hy(t){let e=t.checkIntervalMs??3e4,r=t.isParentAlive??nO,n=!1,o=()=>{n||(n=!0,t.onShutdown())},s=setInterval(()=>{r()||o()},e);s.unref();let i=()=>o();process.stdin.resume(),process.stdin.on("end",i),process.stdin.on("close",i),process.stdin.on("error",i);let a=["SIGTERM","SIGINT"];process.platform!=="win32"&&a.push("SIGHUP");for(let c of a)process.on(c,o);return()=>{n=!0,clearInterval(s),process.stdin.removeListener("end",i),process.stdin.removeListener("close",i),process.stdin.removeListener("error",i);for(let c of a)process.removeListener(c,o)}}var Cv="1.0.
|
|
241
|
+
${o}`}}var rO=process.ppid;function nO(){let t=process.ppid;return!(t!==rO||t===0||t===1)}function Hy(t){let e=t.checkIntervalMs??3e4,r=t.isParentAlive??nO,n=!1,o=()=>{n||(n=!0,t.onShutdown())},s=setInterval(()=>{r()||o()},e);s.unref();let i=()=>o();process.stdin.resume(),process.stdin.on("end",i),process.stdin.on("close",i),process.stdin.on("error",i);let a=["SIGTERM","SIGINT"];process.platform!=="win32"&&a.push("SIGHUP");for(let c of a)process.on(c,o);return()=>{n=!0,clearInterval(s),process.stdin.removeListener("end",i),process.stdin.removeListener("close",i),process.stdin.removeListener("error",i);for(let c of a)process.removeListener(c,o)}}var Cv="1.0.20";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
|
|
242
242
|
`)});process.on("uncaughtException",t=>{process.stderr.write(`[context-mode] uncaughtException: ${t?.message??t}
|
|
243
243
|
`)});var $p=Yi(),DO=ky($p),Mt=new Gi({name:"context-mode",version:Cv}),ss=new Qi({runtimes:$p,projectRoot:process.env.CLAUDE_PROJECT_DIR}),Hn=null;function ZO(t){try{let e=da(Iv(),".claude","context-mode","sessions");if(!Ov(e))return;let r=zO(e).filter(n=>n.endsWith("-events.md"));for(let n of r){let o=da(e,n);try{t.index({path:o,source:"session-events"}),CO(o)}catch{}}}catch{}}function is(){return Hn||(Hn=new ea),ZO(Hn),Hn}var qe={calls:{},bytesReturned:{},bytesIndexed:0,bytesSandboxed:0,sessionStart:Date.now()};function G(t,e){let r=e.content.reduce((n,o)=>n+Buffer.byteLength(o.text),0);return qe.calls[t]=(qe.calls[t]||0)+1,qe.bytesReturned[t]=(qe.bytesReturned[t]||0)+r,e}function yr(t){qe.bytesIndexed+=t}function Tp(t,e){try{let r=Xd(process.env.CLAUDE_PROJECT_DIR),n=Qd(t,r);if(n.decision==="deny")return G(e,{content:[{type:"text",text:`Command blocked by security policy: matches deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}function zv(t,e,r){try{let n=qy(t,e);if(n.length===0)return null;let o=Xd(process.env.CLAUDE_PROJECT_DIR);for(let s of n){let i=Qd(s,o);if(i.decision==="deny")return G(r,{content:[{type:"text",text:`Command blocked by security policy: embedded shell command "${s}" matches deny pattern ${i.matchedPattern}`}],isError:!0})}}catch{}return null}function LO(t,e){try{let r=Ly("Read",process.env.CLAUDE_PROJECT_DIR),n=Uy(t,r);if(n.denied)return G(e,{content:[{type:"text",text:`File access blocked by security policy: path matches Read deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}var UO=DO.join(", "),qO=Gd()?" (Bun detected \u2014 JS/TS runs 3-5x faster)":"",HO="",FO="";function VO(t){let e=[],r=0,n=0;for(;n<t.length;)if(t[n]===HO){for(e.push(r),n++;n<t.length&&t[n]!==FO;)r++,n++;n<t.length&&n++}else r++,n++;return e}function Nv(t,e,r=1500,n){if(t.length<=r)return t;let o=[];if(n)for(let u of VO(n))o.push(u);if(o.length===0){let u=e.toLowerCase().split(/\s+/).filter(d=>d.length>2),l=t.toLowerCase();for(let d of u){let f=l.indexOf(d);for(;f!==-1;)o.push(f),f=l.indexOf(d,f+1)}}if(o.length===0)return t.slice(0,r)+`
|
|
244
244
|
\u2026`;o.sort((u,l)=>u-l);let s=300,i=[];for(let u of o){let l=Math.max(0,u-s),d=Math.min(t.length,u+s);i.length>0&&l<=i[i.length-1][1]?i[i.length-1][1]=d:i.push([l,d])}let a=[],c=0;for(let[u,l]of i){if(c>=r)break;let d=t.slice(u,Math.min(l,u+(r-c)));a.push((u>0?"\u2026":"")+d+(l<t.length?"\u2026":"")),c+=d.length}return a.join(`
|