context-mode 0.9.8 → 0.9.10
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/build/cli.js +70 -95
- package/hooks/pretooluse.mjs +38 -0
- package/package.json +1 -1
- package/skills/upgrade/SKILL.md +17 -2
- package/start.mjs +52 -1
|
@@ -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.
|
|
16
|
+
"version": "0.9.10",
|
|
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.
|
|
3
|
+
"version": "0.9.10",
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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,34 +390,41 @@ async function upgrade() {
|
|
|
370
390
|
timeout: 30000,
|
|
371
391
|
});
|
|
372
392
|
s.stop("Built successfully");
|
|
373
|
-
// Step 3:
|
|
374
|
-
s.start("
|
|
393
|
+
// Step 3: Update in-place (same directory, no registry changes needed)
|
|
394
|
+
s.start("Updating files in-place");
|
|
395
|
+
// Clean stale version dirs from previous upgrade attempts
|
|
375
396
|
const cacheParentMatch = pluginRoot.match(/^(.*[\\/]plugins[\\/]cache[\\/][^\\/]+[\\/][^\\/]+[\\/])/);
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
"start.mjs", "server.bundle.mjs", "package.json", ".mcp.json",
|
|
390
|
-
];
|
|
391
|
-
for (const item of items) {
|
|
392
|
-
try {
|
|
393
|
-
rmSync(resolve(pluginRoot, item), { recursive: true, force: true });
|
|
394
|
-
cpSync(resolve(srcDir, item), resolve(pluginRoot, item), { recursive: true });
|
|
397
|
+
if (cacheParentMatch) {
|
|
398
|
+
const cacheParent = cacheParentMatch[1];
|
|
399
|
+
const myDir = pluginRoot.replace(cacheParent, "").replace(/[\\/]/g, "");
|
|
400
|
+
try {
|
|
401
|
+
const oldDirs = readdirSync(cacheParent).filter(d => d !== myDir);
|
|
402
|
+
for (const d of oldDirs) {
|
|
403
|
+
try {
|
|
404
|
+
rmSync(resolve(cacheParent, d), { recursive: true, force: true });
|
|
405
|
+
}
|
|
406
|
+
catch { /* skip */ }
|
|
407
|
+
}
|
|
408
|
+
if (oldDirs.length > 0) {
|
|
409
|
+
p.log.info(color.dim(` Cleaned ${oldDirs.length} stale cache dir(s)`));
|
|
395
410
|
}
|
|
396
|
-
catch { /* some files may not exist */ }
|
|
397
411
|
}
|
|
412
|
+
catch { /* parent may not exist */ }
|
|
398
413
|
}
|
|
399
|
-
|
|
400
|
-
|
|
414
|
+
// Copy new files over old ones — same path, no registry update needed
|
|
415
|
+
const items = [
|
|
416
|
+
"build", "src", "hooks", "skills", ".claude-plugin",
|
|
417
|
+
"start.mjs", "server.bundle.mjs", "package.json", ".mcp.json",
|
|
418
|
+
];
|
|
419
|
+
for (const item of items) {
|
|
420
|
+
try {
|
|
421
|
+
rmSync(resolve(pluginRoot, item), { recursive: true, force: true });
|
|
422
|
+
cpSync(resolve(srcDir, item), resolve(pluginRoot, item), { recursive: true });
|
|
423
|
+
}
|
|
424
|
+
catch { /* some files may not exist in source */ }
|
|
425
|
+
}
|
|
426
|
+
s.stop(color.green(`Updated in-place to v${newVersion}`));
|
|
427
|
+
// Install production deps (rebuild native modules if needed)
|
|
401
428
|
s.start("Installing production dependencies");
|
|
402
429
|
execSync("npm install --production --no-audit --no-fund", {
|
|
403
430
|
cwd: pluginRoot,
|
|
@@ -405,65 +432,6 @@ async function upgrade() {
|
|
|
405
432
|
timeout: 60000,
|
|
406
433
|
});
|
|
407
434
|
s.stop("Dependencies ready");
|
|
408
|
-
// Step 4: Update installed_plugins.json via spawned script (works from any old version)
|
|
409
|
-
s.start("Updating plugin registry");
|
|
410
|
-
try {
|
|
411
|
-
const scriptPath = resolve(tmpDir + "-post-upgrade.mjs");
|
|
412
|
-
const scriptContent = [
|
|
413
|
-
`import { readFileSync, writeFileSync, readdirSync, rmSync as rm } from "fs";`,
|
|
414
|
-
`import { resolve } from "path";`,
|
|
415
|
-
`import { homedir } from "os";`,
|
|
416
|
-
`const pluginRoot = ${JSON.stringify(pluginRoot)};`,
|
|
417
|
-
`const newVersion = ${JSON.stringify(newVersion)};`,
|
|
418
|
-
`const ipPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");`,
|
|
419
|
-
`try {`,
|
|
420
|
-
` const ipRaw = JSON.parse(readFileSync(ipPath, "utf-8"));`,
|
|
421
|
-
` const plugins = ipRaw.plugins || {};`,
|
|
422
|
-
` let updated = false;`,
|
|
423
|
-
` for (const [key, entries] of Object.entries(plugins)) {`,
|
|
424
|
-
` if (!key.toLowerCase().includes("context-mode")) continue;`,
|
|
425
|
-
` for (const entry of entries) {`,
|
|
426
|
-
` entry.installPath = pluginRoot;`,
|
|
427
|
-
` entry.version = newVersion;`,
|
|
428
|
-
` entry.lastUpdated = new Date().toISOString();`,
|
|
429
|
-
` updated = true;`,
|
|
430
|
-
` }`,
|
|
431
|
-
` }`,
|
|
432
|
-
` if (updated) {`,
|
|
433
|
-
` writeFileSync(ipPath, JSON.stringify(ipRaw, null, 2) + "\\n", "utf-8");`,
|
|
434
|
-
` console.log("REGISTRY_UPDATED");`,
|
|
435
|
-
` }`,
|
|
436
|
-
`} 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
|
-
].join("\n");
|
|
445
|
-
writeFileSync(scriptPath, scriptContent, "utf-8");
|
|
446
|
-
const result = execSync(`node ${scriptPath}`, {
|
|
447
|
-
stdio: "pipe",
|
|
448
|
-
timeout: 10000,
|
|
449
|
-
encoding: "utf-8",
|
|
450
|
-
});
|
|
451
|
-
try {
|
|
452
|
-
rmSync(scriptPath, { force: true });
|
|
453
|
-
}
|
|
454
|
-
catch { /* ignore */ }
|
|
455
|
-
if (result.includes("REGISTRY_UPDATED")) {
|
|
456
|
-
s.stop(color.green("Plugin registry updated"));
|
|
457
|
-
changes.push("Updated plugin registry");
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
s.stop(color.yellow("Plugin registry unchanged"));
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
catch (err) {
|
|
464
|
-
s.stop(color.yellow("Plugin registry update skipped"));
|
|
465
|
-
p.log.warn(color.yellow("Could not update plugin registry") + color.dim(` — ${err instanceof Error ? err.message : String(err)}`));
|
|
466
|
-
}
|
|
467
435
|
// Update global npm package from same GitHub source
|
|
468
436
|
s.start("Updating npm global package");
|
|
469
437
|
try {
|
|
@@ -578,12 +546,19 @@ async function upgrade() {
|
|
|
578
546
|
else {
|
|
579
547
|
p.log.info(color.dim("No changes were needed."));
|
|
580
548
|
}
|
|
581
|
-
// Step 7: Run doctor
|
|
549
|
+
// Step 7: Run doctor from updated pluginRoot
|
|
582
550
|
p.log.step("Running doctor to verify...");
|
|
583
551
|
console.log();
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
552
|
+
try {
|
|
553
|
+
execSync(`node "${resolve(pluginRoot, "build", "cli.js")}" doctor`, {
|
|
554
|
+
stdio: "inherit",
|
|
555
|
+
timeout: 30000,
|
|
556
|
+
cwd: pluginRoot,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
p.log.warn(color.yellow("Doctor had warnings") +
|
|
561
|
+
color.dim(" — restart your Claude Code session to pick up the new version"));
|
|
587
562
|
}
|
|
588
563
|
}
|
|
589
564
|
/* -------------------------------------------------------
|
package/hooks/pretooluse.mjs
CHANGED
|
@@ -6,6 +6,44 @@
|
|
|
6
6
|
* Cross-platform (Windows/macOS/Linux) — no bash/jq dependency.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
10
|
+
import { resolve, dirname, basename } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { homedir, tmpdir } from "node:os";
|
|
13
|
+
|
|
14
|
+
// ─── Self-heal registry (runs once per session via marker file) ───
|
|
15
|
+
try {
|
|
16
|
+
const marker = resolve(tmpdir(), "context-mode-registry-healed");
|
|
17
|
+
if (!existsSync(marker)) {
|
|
18
|
+
const hookDir = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const myRoot = resolve(hookDir, "..");
|
|
20
|
+
const myVersion = basename(myRoot);
|
|
21
|
+
|
|
22
|
+
if (/^\d+\.\d+\.\d+/.test(myVersion)) {
|
|
23
|
+
const ipPath = resolve(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
24
|
+
const ip = JSON.parse(readFileSync(ipPath, "utf-8"));
|
|
25
|
+
let fixed = false;
|
|
26
|
+
|
|
27
|
+
for (const [key, entries] of Object.entries(ip.plugins || {})) {
|
|
28
|
+
if (!key.toLowerCase().includes("context-mode")) continue;
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (entry.installPath !== myRoot) {
|
|
31
|
+
entry.installPath = myRoot;
|
|
32
|
+
entry.version = myVersion;
|
|
33
|
+
entry.lastUpdated = new Date().toISOString();
|
|
34
|
+
fixed = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (fixed) {
|
|
40
|
+
writeFileSync(ipPath, JSON.stringify(ip, null, 2) + "\n", "utf-8");
|
|
41
|
+
}
|
|
42
|
+
writeFileSync(marker, Date.now().toString(), "utf-8");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch { /* best effort — don't block hook */ }
|
|
46
|
+
|
|
9
47
|
let raw = "";
|
|
10
48
|
process.stdin.setEncoding("utf-8");
|
|
11
49
|
for await (const chunk of process.stdin) raw += chunk;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.10",
|
|
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",
|
package/skills/upgrade/SKILL.md
CHANGED
|
@@ -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. **
|
|
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.
|
|
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 {
|