context-mode 1.0.10 → 1.0.12

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.
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code plugins by Mert Koseoğlu",
9
- "version": "1.0.10"
9
+ "version": "1.0.12"
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.10",
16
+ "version": "1.0.12",
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.10",
3
+ "version": "1.0.12",
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
@@ -13,7 +13,7 @@ import { ContentStore, cleanupStaleDBs } from "./store.js";
13
13
  import { readBashPolicies, evaluateCommandDenyOnly, extractShellCommands, readToolDenyPatterns, evaluateFilePath, } from "./security.js";
14
14
  import { detectRuntimes, getRuntimeSummary, getAvailableLanguages, hasBunRuntime, } from "./runtime.js";
15
15
  import { classifyNonZeroExit } from "./exit-classify.js";
16
- const VERSION = "1.0.10";
16
+ const VERSION = "1.0.12";
17
17
  // Prevent silent server death from unhandled async errors
18
18
  process.on("unhandledRejection", (err) => {
19
19
  process.stderr.write(`[context-mode] unhandledRejection: ${err}\n`);
@@ -188,8 +188,12 @@ function extractTask(input) {
188
188
  const TASK_TOOLS = new Set(["TodoWrite", "TaskCreate", "TaskUpdate"]);
189
189
  if (!TASK_TOOLS.has(input.tool_name))
190
190
  return [];
191
+ // Store tool name as type so create vs update can be reliably distinguished
192
+ const type = input.tool_name === "TaskUpdate" ? "task_update"
193
+ : input.tool_name === "TaskCreate" ? "task_create"
194
+ : "task"; // TodoWrite fallback
191
195
  return [{
192
- type: "task",
196
+ type,
193
197
  category: "task",
194
198
  data: truncate(JSON.stringify(input.tool_input), 300),
195
199
  priority: 1,
@@ -86,13 +86,14 @@ export function renderTaskState(taskEvents) {
86
86
  }
87
87
  if (creates.length === 0)
88
88
  return "";
89
+ const DONE = new Set(["completed", "deleted"]);
89
90
  // Match creates to updates positionally (creates[0] → lowest taskId)
90
91
  const sortedIds = Object.keys(updates).sort((a, b) => Number(a) - Number(b));
91
92
  const pending = [];
92
93
  for (let i = 0; i < creates.length; i++) {
93
94
  const matchedId = sortedIds[i];
94
95
  const status = matchedId ? (updates[matchedId] ?? "pending") : "pending";
95
- if (status !== "completed") {
96
+ if (!DONE.has(status)) {
96
97
  pending.push(creates[i]);
97
98
  }
98
99
  }
package/cli.bundle.mjs CHANGED
@@ -282,7 +282,7 @@ async function main() {
282
282
  main();
283
283
  `}async function Rz(){let t=r0();t>0&&console.error(`Cleaned up ${t} stale DB file(s) from previous sessions`);let e=()=>{Rs.cleanupBackgrounded(),co&&co.cleanup()};process.on("exit",e),process.on("SIGINT",()=>{e(),process.exit(0)}),process.on("SIGTERM",()=>{e(),process.exit(0)});let r=new tc;await Yt.connect(r);try{let{detectPlatform:n,getAdapter:o}=await Promise.resolve().then(()=>(Hc(),hh)),s=n(),i=await o(s.platform);if(!i.capabilities.sessionStart){let a=gz(Ps(km(import.meta.url)),".."),c=process.env.CLAUDE_PROJECT_DIR??process.env.CODEX_HOME??process.cwd(),u=i.writeRoutingInstructions(c,a);u&&console.error(`Wrote routing instructions: ${u}`)}}catch{}console.error(`Context Mode MCP server v${y0} running on stdio`),console.error(`Detected runtimes:
284
284
  ${qs(Tm)}`),po()||(console.error(`
285
- Performance tip: Install Bun for 3-5x faster JS/TS execution`),console.error(" curl -fsSL https://bun.sh/install | bash"))}var y0,Tm,_z,Yt,Rs,co,Je,bz,$z,Sz,wz,sc,vn,bm,kz,f0,m0,$m,Sm,b0=$(()=>{"use strict";Mb();qb();pm();hm();n0();d0();Fs();p0();y0="1.0.10";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
285
+ Performance tip: Install Bun for 3-5x faster JS/TS execution`),console.error(" curl -fsSL https://bun.sh/install | bash"))}var y0,Tm,_z,Yt,Rs,co,Je,bz,$z,Sz,wz,sc,vn,bm,kz,f0,m0,$m,Sm,b0=$(()=>{"use strict";Mb();qb();pm();hm();n0();d0();Fs();p0();y0="1.0.11";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
286
286
  `)});process.on("uncaughtException",t=>{process.stderr.write(`[context-mode] uncaughtException: ${t?.message??t}
287
287
  `)});Tm=$n(),_z=Us(Tm),Yt=new Qa({name:"context-mode",version:y0}),Rs=new Es({runtimes:Tm,projectRoot:process.env.CLAUDE_PROJECT_DIR}),co=null;Je={calls:{},bytesReturned:{},bytesIndexed:0,bytesSandboxed:0,sessionStart:Date.now()};bz=_z.join(", "),$z=po()?" (Bun detected \u2014 JS/TS runs 3-5x faster)":"",Sz="",wz="";Yt.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.${$z} Available: ${bz}.
288
288
 
@@ -11,7 +11,7 @@ import "../suppress-stderr.mjs";
11
11
  */
12
12
 
13
13
  import { ROUTING_BLOCK } from "../routing-block.mjs";
14
- import { writeSessionEventsFile, buildSessionDirective, getAllProjectEvents } from "../session-directive.mjs";
14
+ import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
15
15
  import {
16
16
  readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
17
17
  getProjectDir, GEMINI_OPTS,
@@ -43,7 +43,7 @@ try {
43
43
  db.markResumeConsumed(sessionId);
44
44
  }
45
45
 
46
- const events = getAllProjectEvents(db);
46
+ const events = getSessionEvents(db, sessionId);
47
47
  if (events.length > 0) {
48
48
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
49
49
  additionalContext += buildSessionDirective("compact", eventMeta);
@@ -57,7 +57,7 @@ try {
57
57
  const dbPath = getSessionDBPath(OPTS);
58
58
  const db = new SessionDB({ dbPath });
59
59
 
60
- const events = getAllProjectEvents(db);
60
+ const events = getLatestSessionEvents(db);
61
61
  if (events.length > 0) {
62
62
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
63
63
  additionalContext += buildSessionDirective("resume", eventMeta);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shared session directive builder for all platform adaptors.
3
3
  *
4
- * Contains: groupEvents, writeSessionEventsFile, buildSessionDirective, getAllProjectEvents.
4
+ * Contains: groupEvents, writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents.
5
5
  * Each adaptor imports these instead of duplicating the logic.
6
6
  */
7
7
 
@@ -76,13 +76,14 @@ export function writeSessionEventsFile(events, eventsPath) {
76
76
  creates.push(ev.data);
77
77
  }
78
78
  }
79
+ const DONE = new Set(["completed", "deleted"]);
79
80
  const sortedIds = Object.keys(updates).sort((a, b) => Number(a) - Number(b));
80
81
  const pending = [];
81
82
  const completed = [];
82
83
  for (let i = 0; i < creates.length; i++) {
83
84
  const matchedId = sortedIds[i];
84
85
  const status = matchedId ? (updates[matchedId] || "pending") : "pending";
85
- if (status === "completed") {
86
+ if (DONE.has(status)) {
86
87
  completed.push(creates[i]);
87
88
  } else {
88
89
  pending.push(creates[i]);
@@ -234,12 +235,13 @@ export function buildSessionDirective(source, eventMeta) {
234
235
  }
235
236
 
236
237
  if (creates.length > 0) {
238
+ const DONE = new Set(["completed", "deleted"]);
237
239
  const sortedIds = Object.keys(updates).sort((a, b) => Number(a) - Number(b));
238
240
  const pending = [];
239
241
  for (let i = 0; i < creates.length; i++) {
240
242
  const matchedId = sortedIds[i];
241
243
  const status = matchedId ? (updates[matchedId] || "pending") : "pending";
242
- if (status !== "completed") {
244
+ if (!DONE.has(status)) {
243
245
  pending.push(creates[i]);
244
246
  }
245
247
  }
@@ -386,10 +388,22 @@ export function buildSessionDirective(source, eventMeta) {
386
388
  return block;
387
389
  }
388
390
 
389
- // ── Get ALL events for this project (across all session_ids) ──
390
- export function getAllProjectEvents(db) {
391
+ // ── Get events for a specific session (used by compact) ──
392
+ export function getSessionEvents(db, sessionId) {
391
393
  return db.db.prepare(
392
394
  `SELECT session_id, type, category, priority, data, source_hook, created_at
393
- FROM session_events ORDER BY created_at ASC`
394
- ).all();
395
+ FROM session_events WHERE session_id = ? ORDER BY created_at ASC`
396
+ ).all(sessionId);
397
+ }
398
+
399
+ // ── Get events from the most recent session that has events (used by resume) ──
400
+ export function getLatestSessionEvents(db) {
401
+ const latest = db.db.prepare(
402
+ `SELECT m.session_id FROM session_meta m
403
+ JOIN session_events e ON m.session_id = e.session_id
404
+ GROUP BY m.session_id
405
+ ORDER BY m.started_at DESC LIMIT 1`
406
+ ).get();
407
+ if (!latest) return [];
408
+ return getSessionEvents(db, latest.session_id);
395
409
  }
@@ -16,7 +16,7 @@ import "./suppress-stderr.mjs";
16
16
 
17
17
  import { ROUTING_BLOCK } from "./routing-block.mjs";
18
18
  import { readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath } from "./session-helpers.mjs";
19
- import { writeSessionEventsFile, buildSessionDirective, getAllProjectEvents } from "./session-directive.mjs";
19
+ import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "./session-directive.mjs";
20
20
  import { join, dirname } from "node:path";
21
21
  import { fileURLToPath, pathToFileURL } from "node:url";
22
22
  import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
@@ -45,7 +45,7 @@ try {
45
45
  db.markResumeConsumed(sessionId);
46
46
  }
47
47
 
48
- const events = getAllProjectEvents(db);
48
+ const events = getSessionEvents(db, sessionId);
49
49
  if (events.length > 0) {
50
50
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath());
51
51
  additionalContext += buildSessionDirective("compact", eventMeta);
@@ -60,7 +60,7 @@ try {
60
60
  const dbPath = getSessionDBPath();
61
61
  const db = new SessionDB({ dbPath });
62
62
 
63
- const events = getAllProjectEvents(db);
63
+ const events = getLatestSessionEvents(db);
64
64
  if (events.length > 0) {
65
65
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath());
66
66
  additionalContext += buildSessionDirective("resume", eventMeta);
@@ -11,7 +11,7 @@ import "../suppress-stderr.mjs";
11
11
  */
12
12
 
13
13
  import { ROUTING_BLOCK } from "../routing-block.mjs";
14
- import { writeSessionEventsFile, buildSessionDirective, getAllProjectEvents } from "../session-directive.mjs";
14
+ import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "../session-directive.mjs";
15
15
  import {
16
16
  readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath,
17
17
  getProjectDir, VSCODE_OPTS,
@@ -43,7 +43,7 @@ try {
43
43
  db.markResumeConsumed(sessionId);
44
44
  }
45
45
 
46
- const events = getAllProjectEvents(db);
46
+ const events = getSessionEvents(db, sessionId);
47
47
  if (events.length > 0) {
48
48
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
49
49
  additionalContext += buildSessionDirective("compact", eventMeta);
@@ -57,7 +57,7 @@ try {
57
57
  const dbPath = getSessionDBPath(OPTS);
58
58
  const db = new SessionDB({ dbPath });
59
59
 
60
- const events = getAllProjectEvents(db);
60
+ const events = getLatestSessionEvents(db);
61
61
  if (events.length > 0) {
62
62
  const eventMeta = writeSessionEventsFile(events, getSessionEventsPath(OPTS));
63
63
  additionalContext += buildSessionDirective("resume", eventMeta);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
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
@@ -227,7 +227,7 @@ stdout:
227
227
  ${n}
228
228
 
229
229
  stderr:
230
- ${o}`}}var lx="1.0.10";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
230
+ ${o}`}}var lx="1.0.11";process.on("unhandledRejection",t=>{process.stderr.write(`[context-mode] unhandledRejection: ${t}
231
231
  `)});process.on("uncaughtException",t=>{process.stderr.write(`[context-mode] uncaughtException: ${t?.message??t}
232
232
  `)});var hp=Ui(),uz=py(hp),Nt=new Zi({name:"context-mode",version:lx}),Wo=new Hi({runtimes:hp,projectRoot:process.env.CLAUDE_PROJECT_DIR}),Dn=null;function lz(t){try{let e=ra(ux(),".claude","context-mode","sessions");if(!cx(e))return;let r=oz(e).filter(n=>n.endsWith("-events.md"));for(let n of r){let o=ra(e,n);try{t.index({path:o,source:"session-events"}),nz(o)}catch{}}}catch{}}function Yo(){return Dn||(Dn=new Vi),lz(Dn),Dn}var qe={calls:{},bytesReturned:{},bytesIndexed:0,bytesSandboxed:0,sessionStart:Date.now()};function J(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 gr(t){qe.bytesIndexed+=t}function gp(t,e){try{let r=Fd(process.env.CLAUDE_PROJECT_DIR),n=Hd(t,r);if(n.decision==="deny")return J(e,{content:[{type:"text",text:`Command blocked by security policy: matches deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}function dx(t,e,r){try{let n=Iy(t,e);if(n.length===0)return null;let o=Fd(process.env.CLAUDE_PROJECT_DIR);for(let s of n){let i=Hd(s,o);if(i.decision==="deny")return J(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 dz(t,e){try{let r=zy("Read",process.env.CLAUDE_PROJECT_DIR),n=Oy(t,r);if(n.denied)return J(e,{content:[{type:"text",text:`File access blocked by security policy: path matches Read deny pattern ${n.matchedPattern}`}],isError:!0})}catch{}return null}var pz=uz.join(", "),fz=Zd()?" (Bun detected \u2014 JS/TS runs 3-5x faster)":"",mz="",hz="";function gz(t){let e=[],r=0,n=0;for(;n<t.length;)if(t[n]===mz){for(e.push(r),n++;n<t.length&&t[n]!==hz;)r++,n++;n<t.length&&n++}else r++,n++;return e}function px(t,e,r=1500,n){if(t.length<=r)return t;let o=[];if(n)for(let u of gz(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)+`
233
233
  \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(`