context-mode 1.0.56 → 1.0.58

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.
@@ -70,6 +70,8 @@ export function createRoutingBlock(t) {
70
70
 
71
71
  When the user says "ctx upgrade", "ctx-upgrade", "/ctx-upgrade", or asks to update context-mode:
72
72
  → Call the upgrade MCP tool, execute the returned shell command, display results as a checklist.
73
+
74
+ After /clear: call ${t("ctx_stats")}(reset: true) to reset session stats and search index.
73
75
  </ctx_commands>
74
76
  </context_window_protection>`;
75
77
  }
@@ -1,4 +1,4 @@
1
- import{createRequire as l}from"node:module";import{unlinkSync as g}from"node:fs";import{tmpdir as h}from"node:os";import{join as y}from"node:path";var E=class{#t;constructor(t){this.#t=t}pragma(t){let s=this.#t.prepare(`PRAGMA ${t}`).all();if(!s||s.length===0)return;if(s.length>1)return s;let i=Object.values(s[0]);return i.length===1?i[0]:s[0]}exec(t){let e="",s=null;for(let o=0;o<t.length;o++){let a=t[o];if(s)e+=a,a===s&&(s=null);else if(a==="'"||a==='"')e+=a,s=a;else if(a===";"){let d=e.trim();d&&this.#t.prepare(d).run(),e=""}else e+=a}let i=e.trim();return i&&this.#t.prepare(i).run(),this}prepare(t){let e=this.#t.prepare(t);return{run:(...s)=>e.run(...s),get:(...s)=>{let i=e.get(...s);return i===null?void 0:i},all:(...s)=>e.all(...s),iterate:(...s)=>e.iterate(...s)}}transaction(t){return this.#t.transaction(t)}close(){this.#t.close()}},c=null;function R(){if(!c){let r=l(import.meta.url);if(globalThis.Bun){let t=r(["bun","sqlite"].join(":")).Database;c=function(s,i){let o=new t(s,{readonly:i?.readonly,create:!0});return new E(o)}}else c=r("better-sqlite3")}return c}function S(r){r.pragma("journal_mode = WAL"),r.pragma("synchronous = NORMAL")}function v(r){for(let t of["","-wal","-shm"])try{g(r+t)}catch{}}function m(r){try{r.pragma("wal_checkpoint(TRUNCATE)")}catch{}try{r.close()}catch{}}function p(r="context-mode"){return y(h(),`${r}-${process.pid}.db`)}var u=class{#t;#e;constructor(t){let e=R();this.#t=t,this.#e=new e(t,{timeout:5e3}),S(this.#e),this.initSchema(),this.prepareStatements()}get db(){return this.#e}get dbPath(){return this.#t}close(){m(this.#e)}cleanup(){m(this.#e),v(this.#t)}};import{createHash as T}from"node:crypto";import{execFileSync as L}from"node:child_process";function U(){let r=process.env.CONTEXT_MODE_SESSION_SUFFIX;if(r!==void 0)return r?`__${r}`:"";try{let t=process.cwd(),e=L("git",["worktree","list","--porcelain"],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).split(/\r?\n/).find(s=>s.startsWith("worktree "))?.replace("worktree ","")?.trim();if(e&&t!==e)return`__${T("sha256").update(t).digest("hex").slice(0,8)}`}catch{}return""}var N=1e3,f=5,n={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",checkDuplicate:"checkDuplicate",evictLowestPriority:"evictLowestPriority",updateMetaLastEvent:"updateMetaLastEvent",ensureSession:"ensureSession",getSessionStats:"getSessionStats",incrementCompactCount:"incrementCompactCount",upsertResume:"upsertResume",getResume:"getResume",markResumeConsumed:"markResumeConsumed",deleteEvents:"deleteEvents",deleteMeta:"deleteMeta",deleteResume:"deleteResume",getOldSessions:"getOldSessions"},_=class extends u{constructor(t){super(t?.dbPath??p("session"))}stmt(t){return this.stmts.get(t)}initSchema(){try{let e=this.db.pragma("table_xinfo(session_events)").find(s=>s.name==="data_hash");e&&e.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch{}this.db.exec(`
1
+ import{createRequire as h}from"node:module";import{unlinkSync as y}from"node:fs";import{tmpdir as R}from"node:os";import{join as S}from"node:path";var p=class{#t;constructor(t){this.#t=t}pragma(t){let s=this.#t.prepare(`PRAGMA ${t}`).all();if(!s||s.length===0)return;if(s.length>1)return s;let i=Object.values(s[0]);return i.length===1?i[0]:s[0]}exec(t){let e="",s=null;for(let o=0;o<t.length;o++){let a=t[o];if(s)e+=a,a===s&&(s=null);else if(a==="'"||a==='"')e+=a,s=a;else if(a===";"){let c=e.trim();c&&this.#t.prepare(c).run(),e=""}else e+=a}let i=e.trim();return i&&this.#t.prepare(i).run(),this}prepare(t){let e=this.#t.prepare(t);return{run:(...s)=>e.run(...s),get:(...s)=>{let i=e.get(...s);return i===null?void 0:i},all:(...s)=>e.all(...s),iterate:(...s)=>e.iterate(...s)}}transaction(t){return this.#t.transaction(t)}close(){this.#t.close()}},u=null;function v(){if(!u){let r=h(import.meta.url);if(globalThis.Bun){let t=r(["bun","sqlite"].join(":")).Database;u=function(s,i){let o=new t(s,{readonly:i?.readonly,create:!0});return new p(o)}}else u=r("better-sqlite3")}return u}function f(r){r.pragma("journal_mode = WAL"),r.pragma("synchronous = NORMAL")}function L(r){for(let t of["","-wal","-shm"])try{y(r+t)}catch{}}function _(r){try{r.pragma("wal_checkpoint(TRUNCATE)")}catch{}try{r.close()}catch{}}function l(r="context-mode"){return S(R(),`${r}-${process.pid}.db`)}function N(r,t=[100,500,2e3]){let e;for(let s=0;s<=t.length;s++)try{return r()}catch(i){let o=i instanceof Error?i.message:String(i);if(!o.includes("SQLITE_BUSY")&&!o.includes("database is locked"))throw i;if(e=i instanceof Error?i:new Error(o),s<t.length){let a=t[s],c=Date.now();for(;Date.now()-c<a;);}}throw new Error(`SQLITE_BUSY: database is locked after ${t.length} retries. Original error: ${e?.message}`)}var d=Symbol.for("__context_mode_live_dbs__"),m=(()=>{let r=globalThis;return r[d]||(r[d]=new Set,process.on("exit",()=>{for(let t of r[d])try{t.close()}catch{}r[d].clear()})),r[d]})(),E=class{#t;#e;constructor(t){let e=v();this.#t=t,this.#e=new e(t,{timeout:3e4}),m.add(this.#e),f(this.#e),this.initSchema(),this.prepareStatements()}get db(){return this.#e}get dbPath(){return this.#t}close(){m.delete(this.#e),_(this.#e)}withRetry(t){return N(t)}cleanup(){m.delete(this.#e),_(this.#e),L(this.#t)}};import{createHash as g}from"node:crypto";import{execFileSync as O}from"node:child_process";function x(){let r=process.env.CONTEXT_MODE_SESSION_SUFFIX;if(r!==void 0)return r?`__${r}`:"";try{let t=process.cwd(),e=O("git",["worktree","list","--porcelain"],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).split(/\r?\n/).find(s=>s.startsWith("worktree "))?.replace("worktree ","")?.trim();if(e&&t!==e)return`__${g("sha256").update(t).digest("hex").slice(0,8)}`}catch{}return""}var D=1e3,b=5,n={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",checkDuplicate:"checkDuplicate",evictLowestPriority:"evictLowestPriority",updateMetaLastEvent:"updateMetaLastEvent",ensureSession:"ensureSession",getSessionStats:"getSessionStats",incrementCompactCount:"incrementCompactCount",upsertResume:"upsertResume",getResume:"getResume",markResumeConsumed:"markResumeConsumed",deleteEvents:"deleteEvents",deleteMeta:"deleteMeta",deleteResume:"deleteResume",getOldSessions:"getOldSessions"},T=class extends E{constructor(t){super(t?.dbPath??l("session"))}stmt(t){return this.stmts.get(t)}initSchema(){try{let e=this.db.pragma("table_xinfo(session_events)").find(s=>s.name==="data_hash");e&&e.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch{}this.db.exec(`
2
2
  CREATE TABLE IF NOT EXISTS session_events (
3
3
  id INTEGER PRIMARY KEY AUTOINCREMENT,
4
4
  session_id TEXT NOT NULL,
@@ -54,4 +54,4 @@ import{createRequire as l}from"node:module";import{unlinkSync as g}from"node:fs"
54
54
  snapshot = excluded.snapshot,
55
55
  event_count = excluded.event_count,
56
56
  created_at = datetime('now'),
57
- consumed = 0`),t(n.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),t(n.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),t(n.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),t(n.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),t(n.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),t(n.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')")}insertEvent(t,e,s="PostToolUse"){let i=T("sha256").update(e.data).digest("hex").slice(0,16).toUpperCase();this.db.transaction(()=>{if(this.stmt(n.checkDuplicate).get(t,f,e.type,i))return;this.stmt(n.getEventCount).get(t).cnt>=N&&this.stmt(n.evictLowestPriority).run(t),this.stmt(n.insertEvent).run(t,e.type,e.category,e.priority,e.data,s,i),this.stmt(n.updateMetaLastEvent).run(t)})()}getEvents(t,e){let s=e?.limit??1e3,i=e?.type,o=e?.minPriority;return i&&o!==void 0?this.stmt(n.getEventsByTypeAndPriority).all(t,i,o,s):i?this.stmt(n.getEventsByType).all(t,i,s):o!==void 0?this.stmt(n.getEventsByPriority).all(t,o,s):this.stmt(n.getEvents).all(t,s)}getEventCount(t){return this.stmt(n.getEventCount).get(t).cnt}ensureSession(t,e){this.stmt(n.ensureSession).run(t,e)}getSessionStats(t){return this.stmt(n.getSessionStats).get(t)??null}incrementCompactCount(t){this.stmt(n.incrementCompactCount).run(t)}upsertResume(t,e,s){this.stmt(n.upsertResume).run(t,e,s??0)}getResume(t){return this.stmt(n.getResume).get(t)??null}markResumeConsumed(t){this.stmt(n.markResumeConsumed).run(t)}deleteSession(t){this.db.transaction(()=>{this.stmt(n.deleteEvents).run(t),this.stmt(n.deleteResume).run(t),this.stmt(n.deleteMeta).run(t)})()}cleanupOldSessions(t=7){let e=`-${t}`,s=this.stmt(n.getOldSessions).all(e);for(let{session_id:i}of s)this.deleteSession(i);return s.length}};export{_ as SessionDB,U as getWorktreeSuffix};
57
+ consumed = 0`),t(n.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),t(n.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),t(n.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),t(n.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),t(n.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),t(n.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')")}insertEvent(t,e,s="PostToolUse"){let i=g("sha256").update(e.data).digest("hex").slice(0,16).toUpperCase();this.db.transaction(()=>{if(this.stmt(n.checkDuplicate).get(t,b,e.type,i))return;this.stmt(n.getEventCount).get(t).cnt>=D&&this.stmt(n.evictLowestPriority).run(t),this.stmt(n.insertEvent).run(t,e.type,e.category,e.priority,e.data,s,i),this.stmt(n.updateMetaLastEvent).run(t)})()}getEvents(t,e){let s=e?.limit??1e3,i=e?.type,o=e?.minPriority;return i&&o!==void 0?this.stmt(n.getEventsByTypeAndPriority).all(t,i,o,s):i?this.stmt(n.getEventsByType).all(t,i,s):o!==void 0?this.stmt(n.getEventsByPriority).all(t,o,s):this.stmt(n.getEvents).all(t,s)}getEventCount(t){return this.stmt(n.getEventCount).get(t).cnt}ensureSession(t,e){this.stmt(n.ensureSession).run(t,e)}getSessionStats(t){return this.stmt(n.getSessionStats).get(t)??null}incrementCompactCount(t){this.stmt(n.incrementCompactCount).run(t)}upsertResume(t,e,s){this.stmt(n.upsertResume).run(t,e,s??0)}getResume(t){return this.stmt(n.getResume).get(t)??null}markResumeConsumed(t){this.stmt(n.markResumeConsumed).run(t)}deleteSession(t){this.db.transaction(()=>{this.stmt(n.deleteEvents).run(t),this.stmt(n.deleteResume).run(t),this.stmt(n.deleteMeta).run(t)})()}cleanupOldSessions(t=7){let e=`-${t}`,s=this.stmt(n.getOldSessions).all(e);for(let{session_id:i}of s)this.deleteSession(i);return s.length}};export{T as SessionDB,x as getWorktreeSuffix};
@@ -170,3 +170,16 @@ export function getCleanupFlagPath(opts = CLAUDE_OPTS) {
170
170
  mkdirSync(dir, { recursive: true });
171
171
  return join(dir, `${hash}${getWorktreeSuffix()}.cleanup`);
172
172
  }
173
+
174
+ /**
175
+ * Return the per-project clear-stats flag path.
176
+ * Written by SessionStart hook on /clear, read by MCP server to reset stats.
177
+ * Path: ~/<configDir>/context-mode/sessions/<SHA256(projectDir)[:16]>.clear-stats
178
+ */
179
+ export function getClearStatsFlagPath(opts = CLAUDE_OPTS) {
180
+ const projectDir = getProjectDir(opts);
181
+ const hash = createHash("sha256").update(projectDir).digest("hex").slice(0, 16);
182
+ const dir = join(homedir(), opts.configDir, "context-mode", "sessions");
183
+ mkdirSync(dir, { recursive: true });
184
+ return join(dir, `${hash}${getWorktreeSuffix()}.clear-stats`);
185
+ }
@@ -19,7 +19,7 @@ import { createRoutingBlock } from "./routing-block.mjs";
19
19
  import { createToolNamer } from "./core/tool-naming.mjs";
20
20
 
21
21
  const ROUTING_BLOCK = createRoutingBlock(createToolNamer("claude-code"));
22
- import { readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath } from "./session-helpers.mjs";
22
+ import { readStdin, getSessionId, getSessionDBPath, getSessionEventsPath, getCleanupFlagPath, getClearStatsFlagPath } from "./session-helpers.mjs";
23
23
  import { writeSessionEventsFile, buildSessionDirective, getSessionEvents, getLatestSessionEvents } from "./session-directive.mjs";
24
24
  import { createSessionLoaders } from "./session-loaders.mjs";
25
25
  import { join, dirname } from "node:path";
@@ -145,7 +145,10 @@ try {
145
145
  }
146
146
  } catch { /* best effort — never block session start */ }
147
147
  }
148
- // "clear" — no action needed
148
+ // "clear" — signal MCP server to reset session stats
149
+ if (source === "clear") {
150
+ try { writeFileSync(getClearStatsFlagPath(), String(Date.now())); } catch { /* best effort */ }
151
+ }
149
152
  } catch (err) {
150
153
  // Session continuity is best-effort — never block session start
151
154
  try {
@@ -3,7 +3,7 @@
3
3
  "name": "Context Mode",
4
4
  "kind": "tool",
5
5
  "description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
6
- "version": "1.0.56",
6
+ "version": "1.0.58",
7
7
  "sandbox": {
8
8
  "mode": "permissive",
9
9
  "filesystem_access": "full",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.56",
3
+ "version": "1.0.58",
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",