context-mode 0.9.8 → 0.9.9

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.
@@ -13,7 +13,7 @@
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": "0.9.8",
16
+ "version": "0.9.9",
17
17
  "author": {
18
18
  "name": "Mert Koseoğlu"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "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.",
5
5
  "author": {
6
6
  "name": "Mert Koseoğlu",
package/build/cli.js CHANGED
@@ -143,8 +143,18 @@ async function doctor() {
143
143
  let criticalFails = 0;
144
144
  const s = p.spinner();
145
145
  s.start("Running diagnostics");
146
- const runtimes = detectRuntimes();
147
- const available = getAvailableLanguages(runtimes);
146
+ let runtimes;
147
+ let available;
148
+ try {
149
+ runtimes = detectRuntimes();
150
+ available = getAvailableLanguages(runtimes);
151
+ }
152
+ catch {
153
+ s.stop("Diagnostics partial");
154
+ p.log.warn(color.yellow("Could not detect runtimes") + color.dim(" — module may be missing, restart session after upgrade"));
155
+ p.outro(color.yellow("Doctor could not fully run — try again after restarting Claude Code"));
156
+ return 1;
157
+ }
148
158
  s.stop("Diagnostics complete");
149
159
  // Runtime check
150
160
  p.note(getRuntimeSummary(runtimes), "Runtimes");
@@ -189,9 +199,14 @@ async function doctor() {
189
199
  }
190
200
  }
191
201
  catch (err) {
192
- criticalFails++;
193
202
  const message = err instanceof Error ? err.message : String(err);
194
- p.log.error(color.red("Server test: FAIL") + ` — ${message}`);
203
+ if (message.includes("Cannot find module") || message.includes("MODULE_NOT_FOUND")) {
204
+ p.log.warn(color.yellow("Server test: SKIP") + color.dim(" — module not available (restart session after upgrade)"));
205
+ }
206
+ else {
207
+ criticalFails++;
208
+ p.log.error(color.red("Server test: FAIL") + ` — ${message}`);
209
+ }
195
210
  }
196
211
  // Hooks installed
197
212
  p.log.step("Checking hooks configuration...");
@@ -275,11 +290,16 @@ async function doctor() {
275
290
  }
276
291
  }
277
292
  catch (err) {
278
- criticalFails++;
279
293
  const message = err instanceof Error ? err.message : String(err);
280
- p.log.error(color.red("FTS5 / better-sqlite3: FAIL") +
281
- `${message}` +
282
- color.dim("\n Try: npm rebuild better-sqlite3"));
294
+ if (message.includes("Cannot find module") || message.includes("MODULE_NOT_FOUND")) {
295
+ p.log.warn(color.yellow("FTS5 / better-sqlite3: SKIP") + color.dim(" module not available (restart session after upgrade)"));
296
+ }
297
+ else {
298
+ criticalFails++;
299
+ p.log.error(color.red("FTS5 / better-sqlite3: FAIL") +
300
+ ` — ${message}` +
301
+ color.dim("\n Try: npm rebuild better-sqlite3"));
302
+ }
283
303
  }
284
304
  // Version check
285
305
  p.log.step("Checking versions...");
@@ -370,9 +390,26 @@ async function upgrade() {
370
390
  timeout: 30000,
371
391
  });
372
392
  s.stop("Built successfully");
373
- // Step 3: Install fresh into new version directory (don't touch old dir - process runs from it)
374
- s.start("Installing fresh");
393
+ // Step 3: Clean old cache dirs (not our own - we're running from it), then install fresh
375
394
  const cacheParentMatch = pluginRoot.match(/^(.*[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/][^\\/]+[\\/])/);
395
+ if (cacheParentMatch) {
396
+ const cacheParent = cacheParentMatch[1];
397
+ const myDir = pluginRoot.replace(cacheParent, "");
398
+ try {
399
+ const oldDirs = readdirSync(cacheParent).filter(d => d !== myDir);
400
+ for (const d of oldDirs) {
401
+ try {
402
+ rmSync(resolve(cacheParent, d), { recursive: true, force: true });
403
+ }
404
+ catch { /* skip */ }
405
+ }
406
+ if (oldDirs.length > 0) {
407
+ p.log.info(color.dim(` Cleaned ${oldDirs.length} old cache dir(s)`));
408
+ }
409
+ }
410
+ catch { /* parent may not exist */ }
411
+ }
412
+ s.start("Installing fresh");
376
413
  const freshDir = cacheParentMatch
377
414
  ? resolve(cacheParentMatch[1], newVersion)
378
415
  : pluginRoot;
@@ -410,7 +447,7 @@ async function upgrade() {
410
447
  try {
411
448
  const scriptPath = resolve(tmpDir + "-post-upgrade.mjs");
412
449
  const scriptContent = [
413
- `import { readFileSync, writeFileSync, readdirSync, rmSync as rm } from "fs";`,
450
+ `import { readFileSync, writeFileSync } from "fs";`,
414
451
  `import { resolve } from "path";`,
415
452
  `import { homedir } from "os";`,
416
453
  `const pluginRoot = ${JSON.stringify(pluginRoot)};`,
@@ -434,13 +471,6 @@ async function upgrade() {
434
471
  ` console.log("REGISTRY_UPDATED");`,
435
472
  ` }`,
436
473
  `} catch (e) { console.error("REGISTRY_FAILED:" + e.message); }`,
437
- `const curDir = pluginRoot.split(/[\\/\\\\]/).pop();`,
438
- `const parDir = pluginRoot.replace(/[\\/\\\\][^\\/\\\\]+$/, "");`,
439
- `try {`,
440
- ` for (const d of readdirSync(parDir).filter(x => x !== curDir)) {`,
441
- ` try { rm(resolve(parDir, d), { recursive: true, force: true }); console.log("CLEANED:" + d); } catch {}`,
442
- ` }`,
443
- `} catch {}`,
444
474
  ].join("\n");
445
475
  writeFileSync(scriptPath, scriptContent, "utf-8");
446
476
  const result = execSync(`node ${scriptPath}`, {
@@ -578,12 +608,20 @@ async function upgrade() {
578
608
  else {
579
609
  p.log.info(color.dim("No changes were needed."));
580
610
  }
581
- // Step 7: Run doctor
611
+ // Step 7: Run doctor from NEW pluginRoot (not old __dirname)
582
612
  p.log.step("Running doctor to verify...");
583
613
  console.log();
584
- const doctorCode = await doctor();
585
- if (doctorCode !== 0) {
586
- process.exit(doctorCode);
614
+ try {
615
+ const doctorScript = resolve(pluginRoot, "build", "cli.js");
616
+ execSync(`node "${doctorScript}" doctor`, {
617
+ stdio: "inherit",
618
+ timeout: 30000,
619
+ cwd: pluginRoot,
620
+ });
621
+ }
622
+ catch {
623
+ p.log.warn(color.yellow("Doctor had warnings") +
624
+ color.dim(" — restart your Claude Code session to pick up the new version"));
587
625
  }
588
626
  }
589
627
  /* -------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-mode",
3
- "version": "0.9.8",
3
+ "version": "0.9.9",
4
4
  "type": "module",
5
5
  "description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution, FTS5 knowledge base, and intent-driven search.",
6
6
  "author": "Mert Koseoğlu",
@@ -18,14 +18,29 @@ Pull latest from GitHub and reinstall the plugin.
18
18
  ```
19
19
  node "<PLUGIN_ROOT>/build/cli.js" upgrade
20
20
  ```
21
- 3. **IMPORTANT**: After the Bash tool completes, re-display the key results as markdown text directly in the conversation so the user sees them without expanding the tool output. Format as:
21
+ 3. **Registry verification** (CRITICAL do this even if upgrade reported success):
22
+ Run a Bash command that does ALL of the following in a single `node -e` script:
23
+ - Read `~/.claude/plugins/installed_plugins.json`
24
+ - Find the `context-mode@claude-context-mode` entry (or any key containing `context-mode`)
25
+ - List directories in `~/.claude/plugins/cache/claude-context-mode/context-mode/`
26
+ - Find the newest semver directory (e.g., `0.9.9` > `0.7.0`)
27
+ - If `installPath` does NOT already point to the newest directory, update:
28
+ - `installPath` → full path to newest version dir
29
+ - `version` → the newest version string
30
+ - `lastUpdated` → `new Date().toISOString()`
31
+ - Write updated JSON back to the file
32
+ - Print what was changed (or "already correct")
33
+
34
+ 4. **IMPORTANT**: After the Bash tool completes, re-display the key results as markdown text directly in the conversation so the user sees them without expanding the tool output. Format as:
22
35
  ```
23
36
  ## context-mode upgrade
24
37
  - [x] Pulled latest from GitHub
25
- - [x] Built and installed v0.6.2
38
+ - [x] Built and installed v0.9.9
26
39
  - [x] npm global updated
27
40
  - [x] Hooks configured
28
41
  - [x] Permissions set
42
+ - [x] Registry verified
29
43
  - [x] Doctor: all checks PASS
30
44
  ```
31
45
  Use `[x]` for success, `[ ]` for failure. Show the actual version numbers and any warnings.
46
+ Tell the user to **restart their Claude Code session** to pick up the new version.
package/start.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { execSync } from "node:child_process";
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, readFileSync, writeFileSync, readdirSync } from "node:fs";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { homedir } from "node:os";
6
7
 
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
9
  process.chdir(__dirname);
@@ -11,6 +12,56 @@ if (!process.env.CLAUDE_PROJECT_DIR) {
11
12
  process.env.CLAUDE_PROJECT_DIR = process.cwd();
12
13
  }
13
14
 
15
+ // Self-heal: if a newer version dir exists, update registry so next session uses it
16
+ const cacheMatch = __dirname.match(
17
+ /^(.*[\/\\]plugins[\/\\]cache[\/\\][^\/\\]+[\/\\][^\/\\]+[\/\\])([^\/\\]+)$/,
18
+ );
19
+ if (cacheMatch) {
20
+ try {
21
+ const cacheParent = cacheMatch[1];
22
+ const myVersion = cacheMatch[2];
23
+ const dirs = readdirSync(cacheParent).filter((d) =>
24
+ /^\d+\.\d+\.\d+/.test(d),
25
+ );
26
+ if (dirs.length > 1) {
27
+ dirs.sort((a, b) => {
28
+ const pa = a.split(".").map(Number);
29
+ const pb = b.split(".").map(Number);
30
+ for (let i = 0; i < 3; i++) {
31
+ if ((pa[i] ?? 0) !== (pb[i] ?? 0))
32
+ return (pa[i] ?? 0) - (pb[i] ?? 0);
33
+ }
34
+ return 0;
35
+ });
36
+ const newest = dirs[dirs.length - 1];
37
+ if (newest && newest !== myVersion) {
38
+ const ipPath = resolve(
39
+ homedir(),
40
+ ".claude",
41
+ "plugins",
42
+ "installed_plugins.json",
43
+ );
44
+ const ip = JSON.parse(readFileSync(ipPath, "utf-8"));
45
+ for (const [key, entries] of Object.entries(ip.plugins || {})) {
46
+ if (!key.toLowerCase().includes("context-mode")) continue;
47
+ for (const entry of entries) {
48
+ entry.installPath = resolve(cacheParent, newest);
49
+ entry.version = newest;
50
+ entry.lastUpdated = new Date().toISOString();
51
+ }
52
+ }
53
+ writeFileSync(
54
+ ipPath,
55
+ JSON.stringify(ip, null, 2) + "\n",
56
+ "utf-8",
57
+ );
58
+ }
59
+ }
60
+ } catch {
61
+ /* best effort — don't block server startup */
62
+ }
63
+ }
64
+
14
65
  // Ensure native module is available
15
66
  if (!existsSync(resolve(__dirname, "node_modules", "better-sqlite3"))) {
16
67
  try {