context-mode 1.0.70 → 1.0.72

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.70"
9
+ "version": "1.0.72"
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.70",
16
+ "version": "1.0.72",
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.70",
3
+ "version": "1.0.72",
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",
@@ -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.70",
6
+ "version": "1.0.72",
7
7
  "sandbox": {
8
8
  "mode": "permissive",
9
9
  "filesystem_access": "full",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "1.0.70",
3
+ "version": "1.0.72",
4
4
  "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.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
package/build/server.js CHANGED
@@ -3,7 +3,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { createHash } from "node:crypto";
6
- import { existsSync, unlinkSync, readdirSync, readFileSync, rmSync, mkdirSync } from "node:fs";
6
+ import { existsSync, unlinkSync, readdirSync, readFileSync, writeFileSync, rmSync, mkdirSync } from "node:fs";
7
7
  import { join, dirname, resolve } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { homedir, tmpdir } from "node:os";
@@ -56,6 +56,14 @@ const executor = new PolyglotExecutor({
56
56
  runtimes,
57
57
  projectRoot: process.env.CLAUDE_PROJECT_DIR,
58
58
  });
59
+ // ─────────────────────────────────────────────────────────
60
+ // FS read tracking preload for batch_execute
61
+ // ─────────────────────────────────────────────────────────
62
+ // NODE_OPTIONS is denied by the executor's #buildSafeEnv (security).
63
+ // Instead, we inject it as an inline shell env prefix in each batch command.
64
+ // This temp file is loaded via --require when batch commands spawn Node processes.
65
+ const CM_FS_PRELOAD = join(tmpdir(), `cm-fs-preload-${process.pid}.js`);
66
+ writeFileSync(CM_FS_PRELOAD, `(function(){var __cm_fs=0;process.on('exit',function(){if(__cm_fs>0)try{process.stderr.write('__CM_FS__:'+__cm_fs+'\\n')}catch(e){}});try{var f=require('fs');var ors=f.readFileSync;f.readFileSync=function(){var r=ors.apply(this,arguments);if(Buffer.isBuffer(r))__cm_fs+=r.length;else if(typeof r==='string')__cm_fs+=Buffer.byteLength(r);return r;};}catch(e){}})();\n`);
59
67
  // Lazy singleton — no DB overhead unless index/search is used
60
68
  let _store = null;
61
69
  /**
@@ -531,6 +539,19 @@ server.registerTool("ctx_execute", {
531
539
  // The closure approach (function(__cm_req){ var require=...; })(require) correctly
532
540
  // shadows the CJS require for all code inside, including __cm_main().
533
541
  instrumentedCode = `
542
+ // FS read instrumentation — count bytes read via fs.readFileSync/readFile
543
+ let __cm_fs=0;
544
+ process.on('exit',()=>{if(__cm_fs>0)try{process.stderr.write('__CM_FS__:'+__cm_fs+'\\n')}catch{}});
545
+ (function(){
546
+ try{
547
+ var f=typeof require!=='undefined'?require('fs'):null;
548
+ if(!f)return;
549
+ var ors=f.readFileSync;
550
+ f.readFileSync=function(){var r=ors.apply(this,arguments);if(Buffer.isBuffer(r))__cm_fs+=r.length;else if(typeof r==='string')__cm_fs+=Buffer.byteLength(r);return r;};
551
+ var orf=f.readFile;
552
+ if(orf)f.readFile=function(){var a=Array.from(arguments),cb=a.pop();orf.apply(this,a.concat([function(e,d){if(!e&&d){if(Buffer.isBuffer(d))__cm_fs+=d.length;else if(typeof d==='string')__cm_fs+=Buffer.byteLength(d);}cb(e,d);}]));};
553
+ }catch{}
554
+ })();
534
555
  let __cm_net=0;
535
556
  // Report network bytes on process exit — works with both promise and callback patterns.
536
557
  // process.on('exit') fires after all I/O completes, unlike .finally() which fires
@@ -585,6 +606,12 @@ __cm_main().catch(e=>{console.error(e);process.exitCode=1});${background ? '\nse
585
606
  // Clean the metric line from stderr
586
607
  result.stderr = result.stderr.replace(/\n?__CM_NET__:\d+\n?/g, "");
587
608
  }
609
+ // Parse sandbox FS read metrics from stderr
610
+ const fsMatch = result.stderr?.match(/__CM_FS__:(\d+)/);
611
+ if (fsMatch) {
612
+ sessionStats.bytesSandboxed += parseInt(fsMatch[1]);
613
+ result.stderr = result.stderr.replace(/\n?__CM_FS__:\d+\n?/g, "");
614
+ }
588
615
  if (result.timedOut) {
589
616
  const partialOutput = result.stdout?.trim();
590
617
  if (result.backgrounded && partialOutput) {
@@ -1390,6 +1417,10 @@ server.registerTool("ctx_batch_execute", {
1390
1417
  const perCommandOutputs = [];
1391
1418
  const startTime = Date.now();
1392
1419
  let timedOut = false;
1420
+ // Inject NODE_OPTIONS for FS read tracking in spawned Node processes.
1421
+ // The executor denies NODE_OPTIONS in its env (security), so we set it
1422
+ // as an inline shell prefix. This only affects child `node` invocations.
1423
+ const nodeOptsPrefix = `NODE_OPTIONS="--require ${CM_FS_PRELOAD}" `;
1393
1424
  for (const cmd of commands) {
1394
1425
  const elapsed = Date.now() - startTime;
1395
1426
  const remaining = timeout - elapsed;
@@ -1400,10 +1431,20 @@ server.registerTool("ctx_batch_execute", {
1400
1431
  }
1401
1432
  const result = await executor.execute({
1402
1433
  language: "shell",
1403
- code: `${cmd.command} 2>&1`,
1434
+ code: `${nodeOptsPrefix}${cmd.command} 2>&1`,
1404
1435
  timeout: remaining,
1405
1436
  });
1406
- const output = result.stdout || "(no output)";
1437
+ let output = result.stdout || "(no output)";
1438
+ // Parse and strip __CM_FS__ markers emitted by the preload script.
1439
+ // Because 2>&1 merges stderr into stdout, markers appear in output.
1440
+ const fsMatches = output.matchAll(/__CM_FS__:(\d+)/g);
1441
+ let cmdFsBytes = 0;
1442
+ for (const m of fsMatches)
1443
+ cmdFsBytes += parseInt(m[1]);
1444
+ if (cmdFsBytes > 0) {
1445
+ sessionStats.bytesSandboxed += cmdFsBytes;
1446
+ output = output.replace(/__CM_FS__:\d+\n?/g, "");
1447
+ }
1407
1448
  perCommandOutputs.push(`# ${cmd.label}\n\n${output}\n`);
1408
1449
  if (result.timedOut) {
1409
1450
  timedOut = true;
@@ -1517,7 +1558,7 @@ server.registerTool("ctx_stats", {
1517
1558
  try {
1518
1559
  const engine = new AnalyticsEngine(sdb);
1519
1560
  const report = engine.queryAll(sessionStats);
1520
- text = formatReport(report);
1561
+ text = formatReport(report, VERSION, _latestVersion);
1521
1562
  }
1522
1563
  finally {
1523
1564
  sdb.close();
@@ -1527,14 +1568,14 @@ server.registerTool("ctx_stats", {
1527
1568
  // No session DB — build a minimal report from runtime stats only
1528
1569
  const engine = new AnalyticsEngine(createMinimalDb());
1529
1570
  const report = engine.queryAll(sessionStats);
1530
- text = formatReport(report);
1571
+ text = formatReport(report, VERSION, _latestVersion);
1531
1572
  }
1532
1573
  }
1533
1574
  catch {
1534
1575
  // Session DB not available or incompatible — build minimal report from runtime stats
1535
1576
  const engine = new AnalyticsEngine(createMinimalDb());
1536
1577
  const report = engine.queryAll(sessionStats);
1537
- text = formatReport(report);
1578
+ text = formatReport(report, VERSION, _latestVersion);
1538
1579
  }
1539
1580
  return trackResponse("ctx_stats", {
1540
1581
  content: [{ type: "text", text }],
@@ -1808,11 +1849,15 @@ async function main() {
1808
1849
  if (cleaned > 0) {
1809
1850
  console.error(`Cleaned up ${cleaned} stale DB file(s) from previous sessions`);
1810
1851
  }
1811
- // Clean up own DB + backgrounded processes on shutdown
1852
+ // Clean up own DB + backgrounded processes + preload script on shutdown
1812
1853
  const shutdown = () => {
1813
1854
  executor.cleanupBackgrounded();
1814
1855
  if (_store)
1815
1856
  _store.close(); // persist DB for --continue sessions
1857
+ try {
1858
+ unlinkSync(CM_FS_PRELOAD);
1859
+ }
1860
+ catch { /* best effort */ }
1816
1861
  };
1817
1862
  const gracefulShutdown = async () => {
1818
1863
  shutdown();
@@ -1,13 +1,8 @@
1
1
  /**
2
- * AnalyticsEngine — All 27 metrics from SessionDB.
2
+ * AnalyticsEngine — Runtime savings + session continuity reporting.
3
3
  *
4
- * Computes session-level and cross-session analytics using SQL queries
5
- * and JavaScript post-processing. Groups:
6
- *
7
- * Group 1 (SQL Direct): 17 metrics — direct SQL against session tables
8
- * Group 2 (JS Computed): 3 metrics — SQL + JS post-processing
9
- * Group 3 (Runtime): 4 metrics — stubs for server.ts tracking
10
- * Group 4 (New Extractor): 3 metrics — stubs for future extractors
4
+ * Computes context-window savings from runtime stats and queries
5
+ * session continuity data from SessionDB.
11
6
  *
12
7
  * Usage:
13
8
  * const engine = new AnalyticsEngine(sessionDb);
@@ -21,46 +16,6 @@ export interface DatabaseAdapter {
21
16
  all(...params: unknown[]): unknown[];
22
17
  };
23
18
  }
24
- /** Weekly trend data point */
25
- export interface WeeklyTrendRow {
26
- day: string;
27
- sessions: number;
28
- }
29
- /** Category distribution row */
30
- export interface ContinuityRow {
31
- category: string;
32
- count: number;
33
- }
34
- /** Hourly productivity row */
35
- export interface HourlyRow {
36
- hour: string;
37
- count: number;
38
- }
39
- /** Project distribution row */
40
- export interface ProjectRow {
41
- project_dir: string;
42
- sessions: number;
43
- }
44
- /** CLAUDE.md freshness row */
45
- export interface FreshnessRow {
46
- data: string;
47
- last_updated: string;
48
- }
49
- /** Rework rate row */
50
- export interface ReworkRow {
51
- data: string;
52
- edits: number;
53
- }
54
- /** Subagent usage row */
55
- export interface SubagentRow {
56
- data: string;
57
- total: number;
58
- }
59
- /** Skill usage row */
60
- export interface SkillRow {
61
- data: string;
62
- invocations: number;
63
- }
64
19
  /** Context savings result (#1) */
65
20
  export interface ContextSavings {
66
21
  rawBytes: number;
@@ -86,11 +41,6 @@ export interface SandboxIO {
86
41
  inputBytes: number;
87
42
  outputBytes: number;
88
43
  }
89
- /** Pattern insight result (#6) */
90
- export interface PatternInsight {
91
- pattern: string;
92
- confidence: number;
93
- }
94
44
  /** Runtime stats tracked by server.ts during a live session. */
95
45
  export interface RuntimeStats {
96
46
  bytesReturned: Record<string, number>;
@@ -131,56 +81,8 @@ export interface FullReport {
131
81
  /** Session metadata from SessionDB */
132
82
  session: {
133
83
  id: string;
134
- duration_min: number | null;
135
- tool_calls: number;
136
84
  uptime_min: string;
137
85
  };
138
- /** Activity metrics */
139
- activity: {
140
- commits: number;
141
- errors: number;
142
- error_rate_pct: number;
143
- tool_diversity: number;
144
- efficiency_score: number;
145
- commits_per_session_avg: number;
146
- session_outcome: string;
147
- productive_pct: number;
148
- exploratory_pct: number;
149
- };
150
- /** Pattern metrics */
151
- patterns: {
152
- hourly_commits: number[];
153
- weekly_trend: Array<{
154
- day: string;
155
- sessions: number;
156
- }>;
157
- iteration_cycles: number;
158
- rework: Array<{
159
- file: string;
160
- edits: number;
161
- }>;
162
- };
163
- /** Health metrics */
164
- health: {
165
- claude_md_freshness: Array<{
166
- project: string;
167
- days_ago: number | null;
168
- }>;
169
- compactions_this_week: number;
170
- weekly_sessions: number;
171
- permission_denials: number;
172
- };
173
- /** Agent metrics */
174
- agents: {
175
- subagents: Array<{
176
- type: string;
177
- count: number;
178
- }>;
179
- skills: Array<{
180
- name: string;
181
- count: number;
182
- }>;
183
- };
184
86
  /** Session continuity data */
185
87
  continuity: {
186
88
  total_events: number;
@@ -209,117 +111,6 @@ export declare class AnalyticsEngine {
209
111
  * or any object with a prepare() method for direct usage.
210
112
  */
211
113
  constructor(db: DatabaseAdapter);
212
- /**
213
- * #5 Weekly Trend — sessions started per day over the last 7 days.
214
- * Returns an array of { day, sessions } sorted by day.
215
- */
216
- weeklyTrend(): WeeklyTrendRow[];
217
- /**
218
- * #7 Session Continuity — event category distribution for a session.
219
- * Shows what the session focused on (file ops, git, errors, etc.).
220
- */
221
- sessionContinuity(sessionId: string): ContinuityRow[];
222
- /**
223
- * #8 Commit Count — number of git commits made during a session.
224
- * Matches events where category='git' and data contains 'commit'.
225
- */
226
- commitCount(sessionId: string): number;
227
- /**
228
- * #9 Error Count — total error events in a session.
229
- */
230
- errorCount(sessionId: string): number;
231
- /**
232
- * #10 Session Duration — elapsed minutes from session start to last event.
233
- * Returns null if last_event_at is not set (session still initializing).
234
- */
235
- sessionDuration(sessionId: string): number | null;
236
- /**
237
- * #11 Error Rate — percentage of events that are errors in a session.
238
- * Returns 0 for sessions with no events (division by zero protection).
239
- */
240
- errorRate(sessionId: string): number;
241
- /**
242
- * #12 Tool Diversity — number of distinct MCP tools used in a session.
243
- * Higher diversity suggests more sophisticated tool usage.
244
- */
245
- toolDiversity(sessionId: string): number;
246
- /**
247
- * #14 Hourly Productivity — event distribution by hour of day.
248
- * Optionally scoped to a session; omit sessionId for all sessions.
249
- */
250
- hourlyProductivity(sessionId?: string): HourlyRow[];
251
- /**
252
- * #15 Project Distribution — session count per project directory.
253
- * Sorted descending by session count.
254
- */
255
- projectDistribution(): ProjectRow[];
256
- /**
257
- * #16 Compaction Count — number of snapshot compactions for a session.
258
- * Higher counts indicate longer/more active sessions.
259
- */
260
- compactionCount(sessionId: string): number;
261
- /**
262
- * #17 Weekly Session Count — total sessions started in the last 7 days.
263
- */
264
- weeklySessionCount(): number;
265
- /**
266
- * #18 Commits Per Session — average commits across all sessions.
267
- * Returns 0 when no sessions exist (NULLIF prevents division by zero).
268
- */
269
- commitsPerSession(): number;
270
- /**
271
- * #22 CLAUDE.md Freshness — last update timestamp for each rule file.
272
- * Helps identify stale configuration files.
273
- */
274
- claudeMdFreshness(): FreshnessRow[];
275
- /**
276
- * #24 Rework Rate — files edited more than once (indicates iteration/rework).
277
- * Sorted descending by edit count.
278
- */
279
- reworkRate(sessionId?: string): ReworkRow[];
280
- /**
281
- * #25 Session Outcome — classify a session as 'productive' or 'exploratory'.
282
- * Productive: has at least one commit AND last event is not an error.
283
- */
284
- sessionOutcome(sessionId: string): "productive" | "exploratory";
285
- /**
286
- * #26 Subagent Usage — subagent spawn counts grouped by type/purpose.
287
- */
288
- subagentUsage(sessionId: string): SubagentRow[];
289
- /**
290
- * #27 Skill Usage — skill/slash-command invocation frequency.
291
- * Sorted descending by invocation count.
292
- */
293
- skillUsage(sessionId: string): SkillRow[];
294
- /**
295
- * #4 Session Mix — percentage of sessions classified as productive.
296
- * Iterates all sessions and uses #25 (sessionOutcome) to classify each.
297
- */
298
- sessionMix(): {
299
- productive: number;
300
- exploratory: number;
301
- };
302
- /**
303
- * #13 / #20 Efficiency Score — composite score (0-100) measuring session productivity.
304
- *
305
- * Components:
306
- * - Error rate (lower = better): weight 30%
307
- * - Tool diversity (higher = better): weight 20%
308
- * - Commit presence (boolean bonus): weight 25%
309
- * - Rework rate (lower = better): weight 15%
310
- * - Session duration efficiency (moderate = better): weight 10%
311
- *
312
- * Formula: score = 100 - errorPenalty + diversityBonus + commitBonus - reworkPenalty + durationBonus - 40
313
- * The -40 baseline prevents empty sessions from scoring 100.
314
- */
315
- efficiencyScore(sessionId: string): number;
316
- /**
317
- * #23 Iteration Cycles — counts edit-error-fix sequences in a session.
318
- *
319
- * Walks events chronologically and detects patterns where a file event
320
- * is followed by an error event, then another file event.
321
- */
322
- iterationCycles(sessionId: string): number;
323
114
  /**
324
115
  * #1 Context Savings Total — bytes kept out of context window.
325
116
  *
@@ -350,32 +141,21 @@ export declare class AnalyticsEngine {
350
141
  */
351
142
  static sandboxIO(inputBytes: number, outputBytes: number): SandboxIO;
352
143
  /**
353
- * #6 Pattern Detected identifies recurring patterns in a session.
354
- *
355
- * Analyzes category distribution and detects dominant patterns
356
- * (>60% threshold). Falls back to combination detection and
357
- * "balanced" for evenly distributed sessions.
358
- */
359
- patternDetected(sessionId: string): string;
360
- /**
361
- * #21 Permission Denials — count of tool calls blocked by security rules.
362
- *
363
- * Filters error events containing "denied", "blocked", or "permission".
364
- * Stub: ideally requires a dedicated extractor in extract.ts.
365
- */
366
- permissionDenials(sessionId: string): number;
367
- /**
368
- * Build a complete FullReport by merging runtime stats (passed in)
369
- * with all 27 DB-backed metrics and continuity data.
144
+ * Build a FullReport by merging runtime stats (passed in)
145
+ * with continuity data from the DB.
370
146
  *
371
147
  * This is the ONE call that ctx_stats should use.
372
148
  */
373
149
  queryAll(runtimeStats: RuntimeStats): FullReport;
374
150
  }
375
151
  /**
376
- * Render a FullReport as the same markdown output ctx_stats has always produced.
152
+ * Render a FullReport as a before/after comparison developers instantly understand.
377
153
  *
378
- * Preserves the exact output format: Context Window Protection table,
379
- * TTL Cache section, Session Continuity table, and Analytics JSON block.
154
+ * Design rules:
155
+ * - If no savings, show "fresh session" format (no fake percentages)
156
+ * - Active session shows BEFORE vs AFTER -- what would have flooded your conversation vs what actually did
157
+ * - Per-tool table only if 2+ different tools were called
158
+ * - Time gained is the hero metric
159
+ * - Under 15 lines for typical sessions
380
160
  */
381
- export declare function formatReport(report: FullReport): string;
161
+ export declare function formatReport(report: FullReport, version?: string, latestVersion?: string | null): string;