context-mode 1.0.137 → 1.0.138
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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +5 -1
- package/build/adapters/jetbrains-copilot/hooks.d.ts +11 -3
- package/build/adapters/jetbrains-copilot/hooks.js +11 -7
- package/build/adapters/vscode-copilot/hooks.d.ts +27 -3
- package/build/adapters/vscode-copilot/hooks.js +27 -12
- package/build/cli.js +199 -32
- package/build/openclaw-plugin.d.ts +130 -0
- package/build/openclaw-plugin.js +626 -0
- package/build/opencode-plugin.d.ts +122 -0
- package/build/opencode-plugin.js +372 -0
- package/build/pi-extension.d.ts +14 -0
- package/build/pi-extension.js +451 -0
- package/build/server.js +4 -1
- package/build/util/db-lock.d.ts +65 -0
- package/build/util/db-lock.js +166 -0
- package/cli.bundle.mjs +173 -160
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/heal-installed-plugins.mjs +115 -1
- package/scripts/postinstall.mjs +16 -18
- package/server.bundle.mjs +43 -43
- package/start.mjs +11 -14
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.138"
|
|
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.
|
|
16
|
+
"version": "1.0.138",
|
|
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.
|
|
3
|
+
"version": "1.0.138",
|
|
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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.138",
|
|
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.
|
|
6
|
+
"version": "1.0.138",
|
|
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.
|
|
3
|
+
"version": "1.0.138",
|
|
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/README.md
CHANGED
|
@@ -439,6 +439,8 @@ Full configs: [`configs/cursor/hooks.json`](configs/cursor/hooks.json) | [`confi
|
|
|
439
439
|
|
|
440
440
|
**Verify:** In the OpenCode session, type `ctx stats`. Context-mode tools should appear and respond.
|
|
441
441
|
|
|
442
|
+
**Upgrade note:** If an existing config still has `mcp.context-mode`, run `context-mode upgrade`. OpenCode now gets `ctx_*` tools from the plugin; the upgrade removes only `mcp.context-mode` and preserves any other MCP servers.
|
|
443
|
+
|
|
442
444
|
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`. The optional [`AGENTS.md`](configs/opencode/AGENTS.md) file provides routing instructions for model awareness. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts. The `experimental.chat.system.transform` hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).
|
|
443
445
|
|
|
444
446
|
> **Note:** OpenCode lacks a real SessionStart hook ([#14808](https://github.com/sst/opencode/issues/14808), [#5409](https://github.com/sst/opencode/issues/5409)). The plugin uses `experimental.chat.system.transform` as a surrogate — it injects both the routing block and resume snapshots into the system prompt. User-prompt capture uses `chat.message` instead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.
|
|
@@ -481,6 +483,8 @@ Full configs: [`configs/opencode/opencode.json`](configs/opencode/opencode.json)
|
|
|
481
483
|
|
|
482
484
|
**Verify:** In the KiloCode session, type `ctx stats`. Context-mode tools should appear and respond.
|
|
483
485
|
|
|
486
|
+
**Upgrade note:** If an existing config still has `mcp.context-mode`, run `context-mode upgrade`. KiloCode now gets `ctx_*` tools from the plugin; the upgrade removes only `mcp.context-mode` and preserves any other MCP servers.
|
|
487
|
+
|
|
484
488
|
**Routing:** Hooks enforce routing programmatically via `tool.execute.before` and `tool.execute.after`. The optional [`AGENTS.md`](configs/opencode/AGENTS.md) file provides routing instructions for model awareness. The `experimental.session.compacting` hook builds resume snapshots when the conversation compacts. The `experimental.chat.system.transform` hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The `chat.message` hook captures user prompts and decisions (UserPromptSubmit equivalent).
|
|
485
489
|
|
|
486
490
|
> **Note:** KiloCode shares the same plugin architecture as OpenCode, using the OpenCodeAdapter with platform-specific configuration paths (`kilo.json` instead of `opencode.json`, `~/.config/kilo/` instead of `~/.config/opencode/`). Like OpenCode, it lacks a real SessionStart hook — the plugin uses `experimental.chat.system.transform` as a surrogate. User-prompt capture uses `chat.message` instead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.
|
|
@@ -1190,7 +1194,7 @@ Tool call output can be collapsed/expanded with the default Pi's default keybind
|
|
|
1190
1194
|
|
|
1191
1195
|
| Feature | Claude Code | Qwen Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Antigravity | Kiro | Zed | Pi | OMP |
|
|
1192
1196
|
|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
1193
|
-
| MCP Server | Yes | Yes | Yes | Yes | Yes | Yes |
|
|
1197
|
+
| MCP Server / Native Tools | Yes | Yes | Yes | Yes | Yes | Yes | Native plugin | Native plugin | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
|
1194
1198
|
| PreToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) | Plugin |
|
|
1195
1199
|
| PostToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes | -- | Yes (extension) | Plugin |
|
|
1196
1200
|
| SessionStart Hook | Yes | Yes | Yes | Yes | Yes | -- | ✓ (via experimental.chat.system.transform) | ✓ (via experimental.chat.system.transform) | Plugin | Yes | -- | -- | -- | Yes (extension) | Plugin |
|
|
@@ -45,7 +45,15 @@ export declare function isContextModeHook(entry: {
|
|
|
45
45
|
}, hookType: HookType): boolean;
|
|
46
46
|
/**
|
|
47
47
|
* Build the hook command string for a given hook type.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
*
|
|
49
|
+
* Always emits the CLI dispatcher form
|
|
50
|
+
* (`context-mode hook jetbrains-copilot <event>`) — the `pluginRoot`
|
|
51
|
+
* argument is accepted for API compatibility but intentionally ignored.
|
|
52
|
+
*
|
|
53
|
+
* Same Tier C contract as VS Code Copilot (Issue #613):
|
|
54
|
+
* `.github/hooks/context-mode.json` is workspace-committed (team-shared
|
|
55
|
+
* via git). Embedding `process.execPath` or absolute pluginRoot paths
|
|
56
|
+
* leaks PII and breaks cross-machine portability. See
|
|
57
|
+
* src/adapters/vscode-copilot/hooks.ts for the full archaeology.
|
|
50
58
|
*/
|
|
51
|
-
export declare function buildHookCommand(hookType: HookType,
|
|
59
|
+
export declare function buildHookCommand(hookType: HookType, _pluginRoot?: string): string;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { buildNodeCommand } from "../types.js";
|
|
2
1
|
/**
|
|
3
2
|
* adapters/jetbrains-copilot/hooks — JetBrains Copilot hook definitions and matchers.
|
|
4
3
|
*
|
|
@@ -68,16 +67,21 @@ export function isContextModeHook(entry, hookType) {
|
|
|
68
67
|
}
|
|
69
68
|
/**
|
|
70
69
|
* Build the hook command string for a given hook type.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
70
|
+
*
|
|
71
|
+
* Always emits the CLI dispatcher form
|
|
72
|
+
* (`context-mode hook jetbrains-copilot <event>`) — the `pluginRoot`
|
|
73
|
+
* argument is accepted for API compatibility but intentionally ignored.
|
|
74
|
+
*
|
|
75
|
+
* Same Tier C contract as VS Code Copilot (Issue #613):
|
|
76
|
+
* `.github/hooks/context-mode.json` is workspace-committed (team-shared
|
|
77
|
+
* via git). Embedding `process.execPath` or absolute pluginRoot paths
|
|
78
|
+
* leaks PII and breaks cross-machine portability. See
|
|
79
|
+
* src/adapters/vscode-copilot/hooks.ts for the full archaeology.
|
|
73
80
|
*/
|
|
74
|
-
export function buildHookCommand(hookType,
|
|
81
|
+
export function buildHookCommand(hookType, _pluginRoot) {
|
|
75
82
|
const scriptName = HOOK_SCRIPTS[hookType];
|
|
76
83
|
if (!scriptName) {
|
|
77
84
|
throw new Error(`No script defined for hook type: ${hookType}`);
|
|
78
85
|
}
|
|
79
|
-
if (pluginRoot) {
|
|
80
|
-
return buildNodeCommand(`${pluginRoot}/hooks/jetbrains-copilot/${scriptName}`);
|
|
81
|
-
}
|
|
82
86
|
return `context-mode hook jetbrains-copilot ${hookType.toLowerCase()}`;
|
|
83
87
|
}
|
|
@@ -41,7 +41,31 @@ export declare function isContextModeHook(entry: {
|
|
|
41
41
|
}, hookType: HookType): boolean;
|
|
42
42
|
/**
|
|
43
43
|
* Build the hook command string for a given hook type.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
*
|
|
45
|
+
* Always emits the CLI dispatcher form
|
|
46
|
+
* (`context-mode hook vscode-copilot <event>`) — the `pluginRoot` argument
|
|
47
|
+
* is accepted for API compatibility but intentionally ignored.
|
|
48
|
+
*
|
|
49
|
+
* Why the dispatcher form is mandatory here (Issue #613 — Tier C contract):
|
|
50
|
+
* `.github/hooks/context-mode.json` is a **workspace-committed** file
|
|
51
|
+
* (upstream: refs/platforms/vscode-copilot/assets/prompts/skills/
|
|
52
|
+
* agent-customization/references/hooks.md line 7 — "Workspace
|
|
53
|
+
* (team-shared)"). It lands in every teammate's `git status`. Embedding
|
|
54
|
+
* `process.execPath` or any absolute pluginRoot path here:
|
|
55
|
+
* - Leaks PII (username, `C:/Users/<user>/...` paths).
|
|
56
|
+
* - Breaks cross-machine portability (fnm/nvm/volta/brew shims are
|
|
57
|
+
* per-shell-session ephemeral; the path goes stale immediately on
|
|
58
|
+
* Windows + fnm).
|
|
59
|
+
*
|
|
60
|
+
* Commit `f5c9d02` (2026-03-06) added an absolute-path branch when a
|
|
61
|
+
* pluginRoot was passed. It solved a real PATH-availability bug on
|
|
62
|
+
* Brew/nvm setups by going too far — the CLI then always passes
|
|
63
|
+
* pluginRoot, so the portable form became unreachable in production
|
|
64
|
+
* and every `/ctx-upgrade` baked a non-portable command into the
|
|
65
|
+
* committed config. This reverts to the pre-`f5c9d02` shape.
|
|
66
|
+
*
|
|
67
|
+
* For users without a global install, the recovery path is the same as
|
|
68
|
+
* every other CLI-dispatcher adapter (cursor, codex):
|
|
69
|
+
* `npm install -g context-mode`
|
|
46
70
|
*/
|
|
47
|
-
export declare function buildHookCommand(hookType: HookType,
|
|
71
|
+
export declare function buildHookCommand(hookType: HookType, _pluginRoot?: string): string;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { buildNodeCommand } from "../types.js";
|
|
2
1
|
/**
|
|
3
2
|
* adapters/vscode-copilot/hooks — VS Code Copilot hook definitions and matchers.
|
|
4
3
|
*
|
|
@@ -63,21 +62,37 @@ export function isContextModeHook(entry, hookType) {
|
|
|
63
62
|
}
|
|
64
63
|
/**
|
|
65
64
|
* Build the hook command string for a given hook type.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
*
|
|
66
|
+
* Always emits the CLI dispatcher form
|
|
67
|
+
* (`context-mode hook vscode-copilot <event>`) — the `pluginRoot` argument
|
|
68
|
+
* is accepted for API compatibility but intentionally ignored.
|
|
69
|
+
*
|
|
70
|
+
* Why the dispatcher form is mandatory here (Issue #613 — Tier C contract):
|
|
71
|
+
* `.github/hooks/context-mode.json` is a **workspace-committed** file
|
|
72
|
+
* (upstream: refs/platforms/vscode-copilot/assets/prompts/skills/
|
|
73
|
+
* agent-customization/references/hooks.md line 7 — "Workspace
|
|
74
|
+
* (team-shared)"). It lands in every teammate's `git status`. Embedding
|
|
75
|
+
* `process.execPath` or any absolute pluginRoot path here:
|
|
76
|
+
* - Leaks PII (username, `C:/Users/<user>/...` paths).
|
|
77
|
+
* - Breaks cross-machine portability (fnm/nvm/volta/brew shims are
|
|
78
|
+
* per-shell-session ephemeral; the path goes stale immediately on
|
|
79
|
+
* Windows + fnm).
|
|
80
|
+
*
|
|
81
|
+
* Commit `f5c9d02` (2026-03-06) added an absolute-path branch when a
|
|
82
|
+
* pluginRoot was passed. It solved a real PATH-availability bug on
|
|
83
|
+
* Brew/nvm setups by going too far — the CLI then always passes
|
|
84
|
+
* pluginRoot, so the portable form became unreachable in production
|
|
85
|
+
* and every `/ctx-upgrade` baked a non-portable command into the
|
|
86
|
+
* committed config. This reverts to the pre-`f5c9d02` shape.
|
|
87
|
+
*
|
|
88
|
+
* For users without a global install, the recovery path is the same as
|
|
89
|
+
* every other CLI-dispatcher adapter (cursor, codex):
|
|
90
|
+
* `npm install -g context-mode`
|
|
68
91
|
*/
|
|
69
|
-
export function buildHookCommand(hookType,
|
|
92
|
+
export function buildHookCommand(hookType, _pluginRoot) {
|
|
70
93
|
const scriptName = HOOK_SCRIPTS[hookType];
|
|
71
94
|
if (!scriptName) {
|
|
72
95
|
throw new Error(`No script defined for hook type: ${hookType}`);
|
|
73
96
|
}
|
|
74
|
-
if (pluginRoot) {
|
|
75
|
-
// v1.0.107 fix — was `${pluginRoot}/hooks/${scriptName}` which resolved to
|
|
76
|
-
// the Claude-Code generic hook (`hooks/pretooluse.mjs`) instead of the
|
|
77
|
-
// VSCode-specific wrapper at `hooks/vscode-copilot/pretooluse.mjs`. JetBrains
|
|
78
|
-
// adapter already had the correct subdir (jetbrains-copilot/hooks.ts:98)
|
|
79
|
-
// so this brings VSCode to parity.
|
|
80
|
-
return buildNodeCommand(`${pluginRoot}/hooks/vscode-copilot/${scriptName}`);
|
|
81
|
-
}
|
|
82
97
|
return `context-mode hook vscode-copilot ${hookType.toLowerCase()}`;
|
|
83
98
|
}
|
package/build/cli.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import * as p from "@clack/prompts";
|
|
15
15
|
import color from "picocolors";
|
|
16
16
|
import { execFileSync, execSync, execFile as nodeExecFile } from "node:child_process";
|
|
17
|
-
import { readFileSync,
|
|
17
|
+
import { readFileSync, cpSync, accessSync, existsSync, readdirSync, rmSync, closeSync, openSync, chmodSync, constants } from "node:fs";
|
|
18
18
|
import { request as httpsRequest } from "node:https";
|
|
19
19
|
import { resolve, dirname, join } from "node:path";
|
|
20
20
|
import { tmpdir, devNull, homedir } from "node:os";
|
|
@@ -27,7 +27,7 @@ import { discoverSiblingMcpPids, killSiblingMcpServers } from "./util/sibling-mc
|
|
|
27
27
|
// v1.0.119 — Issue #523 Layer 5 heal: post-bump assertion on .claude-plugin/plugin.json
|
|
28
28
|
// mcpServers args. Single source of truth shared with start.mjs HEAL block + postinstall.
|
|
29
29
|
// @ts-expect-error — JS module, no TS declarations
|
|
30
|
-
import { healPluginJsonMcpServers,
|
|
30
|
+
import { healPluginJsonMcpServers, sweepStaleMcpJson } from "../scripts/heal-installed-plugins.mjs";
|
|
31
31
|
// @ts-expect-error — JS module, no TS declarations
|
|
32
32
|
import { detectWindowsVsYear } from "../scripts/heal-better-sqlite3.mjs";
|
|
33
33
|
// Private 16-LOC copy of browserOpenArgv. Canonical version lives in src/server.ts;
|
|
@@ -454,6 +454,166 @@ async function doctor() {
|
|
|
454
454
|
p.log.warn(color.yellow("Plugin enabled: WARN") +
|
|
455
455
|
` — ${pluginCheck.message}`);
|
|
456
456
|
}
|
|
457
|
+
// ── Issue #613 — proactive Tier C absolute-path detection ───────────
|
|
458
|
+
// PR #620 fixed `buildHookCommand` for vscode-copilot + jetbrains-copilot
|
|
459
|
+
// so future writes are CLI-dispatcher-shape. But users who ran
|
|
460
|
+
// /ctx-upgrade on v1.0.136 or earlier are still carrying poisoned
|
|
461
|
+
// committable files in their workspace:
|
|
462
|
+
// - `.github/hooks/context-mode.json` (vscode-copilot, team-shared)
|
|
463
|
+
// - `.jetbrains/copilot/hooks.json` (jetbrains-copilot, team-shared)
|
|
464
|
+
// - `.cursor/hooks.json` (cursor, team-shared)
|
|
465
|
+
// Per ISSUE-613-VERDICT §6.1 these are Tier C — workspace-committed
|
|
466
|
+
// cross-machine config. Doctor scans them for absolute paths and
|
|
467
|
+
// fnm_multishells shims; if found, FAIL with `ctx_upgrade` remediation.
|
|
468
|
+
// Per ISSUE-604-VERDICT §11 ("silent-green doctor while hooks are dead
|
|
469
|
+
// is itself a P0 trust bug") — surface poison BEFORE the user hits a
|
|
470
|
+
// runtime failure.
|
|
471
|
+
p.log.step("Checking team-shared hook configs in your workspace...");
|
|
472
|
+
{
|
|
473
|
+
const projectDir = process.cwd();
|
|
474
|
+
const tierCFiles = [
|
|
475
|
+
".github/hooks/context-mode.json",
|
|
476
|
+
".cursor/hooks.json",
|
|
477
|
+
".jetbrains/copilot/hooks.json",
|
|
478
|
+
];
|
|
479
|
+
let tierCFails = 0;
|
|
480
|
+
let tierCChecked = 0;
|
|
481
|
+
// Detect absolute-path patterns that should never appear in a
|
|
482
|
+
// workspace-committed config. Per Mert's standing Windows-safety rule:
|
|
483
|
+
// handle both `/` and `\\` separators.
|
|
484
|
+
function isAbsoluteOrShimPath(s) {
|
|
485
|
+
// unix absolute
|
|
486
|
+
if (s.startsWith("/"))
|
|
487
|
+
return true;
|
|
488
|
+
// Windows drive-letter absolute (e.g. C:/, C:\)
|
|
489
|
+
if (/^[A-Za-z]:[/\\]/.test(s))
|
|
490
|
+
return true;
|
|
491
|
+
// Windows UNC or escaped-backslash absolute fragments
|
|
492
|
+
if (s.includes("\\\\"))
|
|
493
|
+
return true;
|
|
494
|
+
// fnm shim hint — issue #613 reporter's exact stderr shape
|
|
495
|
+
if (s.includes("fnm_multishells"))
|
|
496
|
+
return true;
|
|
497
|
+
// process.execPath literal baked into JSON
|
|
498
|
+
if (s.includes("process.execPath"))
|
|
499
|
+
return true;
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
function recurseStrings(node, hit) {
|
|
503
|
+
if (typeof node === "string") {
|
|
504
|
+
hit(node);
|
|
505
|
+
}
|
|
506
|
+
else if (Array.isArray(node)) {
|
|
507
|
+
for (const item of node)
|
|
508
|
+
recurseStrings(item, hit);
|
|
509
|
+
}
|
|
510
|
+
else if (node && typeof node === "object") {
|
|
511
|
+
for (const v of Object.values(node))
|
|
512
|
+
recurseStrings(v, hit);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
for (const rel of tierCFiles) {
|
|
516
|
+
const abs = resolve(projectDir, rel);
|
|
517
|
+
if (!existsSync(abs))
|
|
518
|
+
continue; // missing config → SKIP, no false fail
|
|
519
|
+
tierCChecked++;
|
|
520
|
+
try {
|
|
521
|
+
const parsed = JSON.parse(readFileSync(abs, "utf-8"));
|
|
522
|
+
const offenders = [];
|
|
523
|
+
recurseStrings(parsed, (s) => {
|
|
524
|
+
if (isAbsoluteOrShimPath(s))
|
|
525
|
+
offenders.push(s);
|
|
526
|
+
});
|
|
527
|
+
if (offenders.length > 0) {
|
|
528
|
+
criticalFails++;
|
|
529
|
+
tierCFails++;
|
|
530
|
+
// Truncate to one example to keep output readable; show count.
|
|
531
|
+
const example = offenders[0].length > 100
|
|
532
|
+
? offenders[0].slice(0, 97) + "..."
|
|
533
|
+
: offenders[0];
|
|
534
|
+
p.log.error(color.red(`Hook config: FAIL`) +
|
|
535
|
+
` — ${rel} has your machine's local paths baked in` +
|
|
536
|
+
color.dim("\n This file is committed to git, so teammates and CI will get your path and the hooks will break for them." +
|
|
537
|
+
`\n Found ${offenders.length} hard-coded path(s), e.g.: ${example}` +
|
|
538
|
+
"\n Fix: run /context-mode:ctx-upgrade — it rewrites the file to a portable form that works on every machine." +
|
|
539
|
+
"\n Details: https://github.com/mksglu/context-mode/issues/613"));
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
p.log.success(color.green("Hook config: PASS") +
|
|
543
|
+
color.dim(` — ${rel} is portable (no hard-coded paths)`));
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
catch (err) {
|
|
547
|
+
// Malformed JSON should not crash doctor; warn and move on.
|
|
548
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
549
|
+
p.log.warn(color.yellow(`Hook config: WARN`) +
|
|
550
|
+
` — ${rel} is not valid JSON` +
|
|
551
|
+
color.dim("\n Doctor cannot scan it for portability issues until the file parses." +
|
|
552
|
+
"\n Fix: open the file and check it in a JSON validator, or delete it and run /context-mode:ctx-upgrade to regenerate." +
|
|
553
|
+
`\n Parser said: ${msg.slice(0, 160)}`));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (tierCChecked === 0) {
|
|
557
|
+
p.log.info(color.dim("Hook config: SKIP — no team-shared hook configs found in this workspace"));
|
|
558
|
+
}
|
|
559
|
+
else if (tierCFails === 0) {
|
|
560
|
+
// already individual PASS messages above; no need for a summary
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// ── Issue #609 — proactive stale `.mcp.json` detection ──────────────
|
|
564
|
+
// PR #620 deleted the per-version cache `.mcp.json` write from cli.ts
|
|
565
|
+
// and shipped `sweepStaleMcpJson` to clean up any pre-existing copies.
|
|
566
|
+
// But users on the field may still have stale `.mcp.json` files left
|
|
567
|
+
// by /ctx-upgrade flows that ran before PR #620 (or by Claude Code's
|
|
568
|
+
// native auto-update copying a poisoned file forward). Surface those
|
|
569
|
+
// as WARN (recoverable — next ctx_upgrade sweeps them) so the user
|
|
570
|
+
// knows what to do instead of being told everything is green while
|
|
571
|
+
// the file lingers on disk.
|
|
572
|
+
// Per ISSUE-604-VERDICT §11 same trust contract as Tier C check above.
|
|
573
|
+
p.log.step("Checking for leftover .mcp.json files from older versions...");
|
|
574
|
+
{
|
|
575
|
+
const cacheRoot = join(homedir(), ".claude", "plugins", "cache", "context-mode", "context-mode");
|
|
576
|
+
if (!existsSync(cacheRoot)) {
|
|
577
|
+
p.log.info(color.dim("Leftover .mcp.json check: SKIP — no plugin cache exists yet (Claude Code has not installed context-mode here)"));
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
let staleCount = 0;
|
|
581
|
+
const staleVersions = [];
|
|
582
|
+
try {
|
|
583
|
+
const versionDirs = readdirSync(cacheRoot);
|
|
584
|
+
for (const v of versionDirs) {
|
|
585
|
+
const candidate = join(cacheRoot, v, ".mcp.json");
|
|
586
|
+
if (existsSync(candidate)) {
|
|
587
|
+
staleCount++;
|
|
588
|
+
if (staleVersions.length < 5)
|
|
589
|
+
staleVersions.push(v);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
595
|
+
p.log.warn(color.yellow("Leftover .mcp.json check: WARN") +
|
|
596
|
+
` — could not read the plugin cache directory` +
|
|
597
|
+
color.dim(`\n Path: ${cacheRoot}` +
|
|
598
|
+
`\n Reason: ${msg.slice(0, 160)}` +
|
|
599
|
+
"\n Fix: check that the directory is readable, then re-run doctor. If the issue persists, run /context-mode:ctx-upgrade."));
|
|
600
|
+
staleCount = 0;
|
|
601
|
+
}
|
|
602
|
+
if (staleCount === 0) {
|
|
603
|
+
p.log.success(color.green("Leftover .mcp.json check: PASS") +
|
|
604
|
+
color.dim(" — no old .mcp.json files in the plugin cache"));
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
// WARN, not FAIL — per architect spec this is recoverable.
|
|
608
|
+
p.log.warn(color.yellow("Leftover .mcp.json check: WARN") +
|
|
609
|
+
` — found ${staleCount} old .mcp.json file(s) left over from previous context-mode versions` +
|
|
610
|
+
color.dim("\n These are harmless but should be cleaned up so they cannot confuse Claude Code after an auto-update." +
|
|
611
|
+
`\n Versions affected: ${staleVersions.join(", ")}${staleCount > staleVersions.length ? ", ..." : ""}` +
|
|
612
|
+
"\n Fix: run /context-mode:ctx-upgrade — it sweeps these files automatically on the next run." +
|
|
613
|
+
"\n Details: https://github.com/mksglu/context-mode/issues/609"));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
457
617
|
// FTS5 / SQLite
|
|
458
618
|
p.log.step("Checking FTS5 / SQLite...");
|
|
459
619
|
try {
|
|
@@ -798,20 +958,24 @@ async function upgrade(opts) {
|
|
|
798
958
|
}
|
|
799
959
|
catch { /* some files may not exist in source */ }
|
|
800
960
|
}
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
// ${CLAUDE_PLUGIN_ROOT} placeholder
|
|
805
|
-
//
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
961
|
+
// Issue #609 — DO NOT write `.mcp.json` into the plugin cache dir.
|
|
962
|
+
//
|
|
963
|
+
// Historical context: #411 fixed an absolute-path bake by writing the
|
|
964
|
+
// ${CLAUDE_PLUGIN_ROOT} placeholder form here. #531 (commit 9261377)
|
|
965
|
+
// removed `.mcp.json` from `package.json files[]` so the npm tarball
|
|
966
|
+
// stopped shipping it. But the cli-side write persisted, so every
|
|
967
|
+
// /ctx-upgrade re-baked one. When Claude Code's native plugin manager
|
|
968
|
+
// auto-update later carries a previous version's `.mcp.json` forward
|
|
969
|
+
// into a fresh version dir, the stale start.mjs absolute path goes
|
|
970
|
+
// with it → MODULE_NOT_FOUND on every MCP boot.
|
|
971
|
+
//
|
|
972
|
+
// Architectural fix: Claude Code reads `.claude-plugin/plugin.json`
|
|
973
|
+
// .mcpServers as the canonical source (upstream:
|
|
974
|
+
// refs/platforms/claude-code/src/utils/plugins/mcpPluginIntegration.ts:131-212).
|
|
975
|
+
// `.mcp.json` is a redundant per-version artifact whose only role
|
|
976
|
+
// historically was to be a write-time poison vector. Don't write it.
|
|
977
|
+
// The post-bump cache-sweep below removes any pre-existing copies so
|
|
978
|
+
// the previous-version-carry vector cannot replay.
|
|
815
979
|
// Normalize hooks.json + plugin.json against the REAL pluginRoot now that
|
|
816
980
|
// files have been copied. Two reasons:
|
|
817
981
|
// 1. If a prior buggy postinstall (or any future regression) baked the
|
|
@@ -908,30 +1072,33 @@ async function upgrade(opts) {
|
|
|
908
1072
|
const message = err instanceof Error ? err.message : String(err);
|
|
909
1073
|
throw new Error(`plugin.json drift check failed: ${message}`);
|
|
910
1074
|
}
|
|
911
|
-
//
|
|
912
|
-
//
|
|
913
|
-
//
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
//
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
//
|
|
1075
|
+
// Issue #609 — Layer 6 replacement: sweep stale `.mcp.json` files from
|
|
1076
|
+
// every per-version cache dir. Supersedes the previous healMcpJsonArgs
|
|
1077
|
+
// drift-check block (v1.0.122) — that block existed because cli.ts
|
|
1078
|
+
// itself wrote `.mcp.json`. With the write gone (above), the only
|
|
1079
|
+
// remaining `.mcp.json` files are stale carry-forwards from earlier
|
|
1080
|
+
// versions. Sweep them so Claude Code's auto-update can't replay them
|
|
1081
|
+
// into a fresh version dir.
|
|
1082
|
+
//
|
|
1083
|
+
// Belt-and-braces: a second sweep call MUST report removed:[] or we
|
|
1084
|
+
// throw — same architectural-lock pattern as the plugin.json drift
|
|
1085
|
+
// check above. Single source of truth shared with start.mjs HEAL
|
|
1086
|
+
// block + postinstall.
|
|
920
1087
|
try {
|
|
921
1088
|
const pluginCacheRoot = resolve(resolveClaudeConfigDir(), "plugins", "cache");
|
|
922
1089
|
const pluginKey = "context-mode@context-mode";
|
|
923
|
-
const
|
|
924
|
-
if (
|
|
925
|
-
|
|
1090
|
+
const firstSweep = sweepStaleMcpJson({ pluginCacheRoot, pluginKey });
|
|
1091
|
+
if (firstSweep && firstSweep.removed && firstSweep.removed.length > 0) {
|
|
1092
|
+
p.log.info(color.dim(` Swept ${firstSweep.removed.length} stale .mcp.json file(s) from cache`));
|
|
926
1093
|
}
|
|
927
|
-
const
|
|
928
|
-
if (
|
|
929
|
-
throw new Error(`.mcp.json drift:
|
|
1094
|
+
const secondSweep = sweepStaleMcpJson({ pluginCacheRoot, pluginKey });
|
|
1095
|
+
if (secondSweep && Array.isArray(secondSweep.removed) && secondSweep.removed.length > 0) {
|
|
1096
|
+
throw new Error(`.mcp.json sweep drift: ${secondSweep.removed.length} file(s) still present after first pass`);
|
|
930
1097
|
}
|
|
931
1098
|
}
|
|
932
1099
|
catch (err) {
|
|
933
1100
|
const message = err instanceof Error ? err.message : String(err);
|
|
934
|
-
throw new Error(`.mcp.json
|
|
1101
|
+
throw new Error(`.mcp.json sweep check failed: ${message}`);
|
|
935
1102
|
}
|
|
936
1103
|
// v1.0.X — Layer 7 heal: update user-level ~/.claude.json MCP server
|
|
937
1104
|
// registrations that point to old context-mode version dirs.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw TypeScript plugin entry point for context-mode.
|
|
3
|
+
*
|
|
4
|
+
* Exports an object with { id, name, configSchema, register(api) } for
|
|
5
|
+
* declarative metadata and config validation before code execution.
|
|
6
|
+
*
|
|
7
|
+
* register(api) registers:
|
|
8
|
+
* - before_tool_call hook — Routing enforcement (deny/modify/passthrough)
|
|
9
|
+
* - after_tool_call hook — Session event capture
|
|
10
|
+
* - command:new hook — Session initialization and cleanup
|
|
11
|
+
* - session_start hook — Re-key DB session to OpenClaw's session ID
|
|
12
|
+
* - before_compaction hook — Flush events to resume snapshot
|
|
13
|
+
* - after_compaction hook — Increment compact count
|
|
14
|
+
* - before_prompt_build (p=10) — Resume snapshot injection into system context
|
|
15
|
+
* - before_prompt_build (p=5) — Routing instruction injection into system context
|
|
16
|
+
* - context-mode engine — Context engine with compaction management
|
|
17
|
+
* - /ctx-stats command — Auto-reply command for session statistics
|
|
18
|
+
* - /ctx-doctor command — Auto-reply command for diagnostics
|
|
19
|
+
* - /ctx-upgrade command — Auto-reply command for upgrade
|
|
20
|
+
*
|
|
21
|
+
* Loaded by OpenClaw via: openclaw.extensions entry in package.json
|
|
22
|
+
*
|
|
23
|
+
* OpenClaw plugin paradigm:
|
|
24
|
+
* - Plugins export { id, name, configSchema, register(api) } for metadata
|
|
25
|
+
* - api.registerHook() for event-driven hooks
|
|
26
|
+
* - api.on() for typed lifecycle hooks
|
|
27
|
+
* - api.registerContextEngine() for compaction ownership
|
|
28
|
+
* - api.registerCommand() for auto-reply slash commands
|
|
29
|
+
* - Plugins run in-process with the Gateway (trusted code)
|
|
30
|
+
*/
|
|
31
|
+
import type { OpenClawToolDef } from "./openclaw/mcp-tools.js";
|
|
32
|
+
/** Context for auto-reply command handlers. */
|
|
33
|
+
interface CommandContext {
|
|
34
|
+
senderId?: string;
|
|
35
|
+
channel?: string;
|
|
36
|
+
isAuthorizedSender?: boolean;
|
|
37
|
+
args?: string;
|
|
38
|
+
commandBody?: string;
|
|
39
|
+
config?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
/** OpenClaw plugin API provided to the register function. */
|
|
42
|
+
interface OpenClawPluginApi {
|
|
43
|
+
registerHook(event: string, handler: (...args: unknown[]) => unknown, meta: {
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
}): void;
|
|
47
|
+
/**
|
|
48
|
+
* Register a typed lifecycle hook.
|
|
49
|
+
* Supported names: "session_start", "before_compaction", "after_compaction",
|
|
50
|
+
* "before_prompt_build"
|
|
51
|
+
*/
|
|
52
|
+
on(event: string, handler: (...args: unknown[]) => unknown, opts?: {
|
|
53
|
+
priority?: number;
|
|
54
|
+
}): void;
|
|
55
|
+
registerContextEngine(id: string, factory: () => ContextEngineInstance): void;
|
|
56
|
+
registerCommand?(cmd: {
|
|
57
|
+
name: string;
|
|
58
|
+
description: string;
|
|
59
|
+
acceptsArgs?: boolean;
|
|
60
|
+
requireAuth?: boolean;
|
|
61
|
+
handler: (ctx: CommandContext) => {
|
|
62
|
+
text: string;
|
|
63
|
+
} | Promise<{
|
|
64
|
+
text: string;
|
|
65
|
+
}>;
|
|
66
|
+
}): void;
|
|
67
|
+
registerCli?(factory: (ctx: {
|
|
68
|
+
program: unknown;
|
|
69
|
+
}) => void, meta: {
|
|
70
|
+
commands: string[];
|
|
71
|
+
}): void;
|
|
72
|
+
/**
|
|
73
|
+
* Register an agent tool (OpenClaw native registerTool) — see
|
|
74
|
+
* refs/platforms/openclaw/docs/plugins/building-plugins.md:116. Optional in
|
|
75
|
+
* the type so we degrade silently on legacy hosts that pre-date this API.
|
|
76
|
+
*/
|
|
77
|
+
registerTool?(tool: OpenClawToolDef, opts?: {
|
|
78
|
+
optional?: boolean;
|
|
79
|
+
}): void;
|
|
80
|
+
logger?: {
|
|
81
|
+
info: (...args: unknown[]) => void;
|
|
82
|
+
error: (...args: unknown[]) => void;
|
|
83
|
+
debug?: (...args: unknown[]) => void;
|
|
84
|
+
warn?: (...args: unknown[]) => void;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Context engine instance returned by the factory. */
|
|
88
|
+
interface ContextEngineInstance {
|
|
89
|
+
info: {
|
|
90
|
+
id: string;
|
|
91
|
+
name: string;
|
|
92
|
+
ownsCompaction: boolean;
|
|
93
|
+
};
|
|
94
|
+
ingest(data: unknown): Promise<{
|
|
95
|
+
ingested: boolean;
|
|
96
|
+
}>;
|
|
97
|
+
assemble(ctx: {
|
|
98
|
+
messages: unknown[];
|
|
99
|
+
}): Promise<{
|
|
100
|
+
messages: unknown[];
|
|
101
|
+
estimatedTokens: number;
|
|
102
|
+
}>;
|
|
103
|
+
compact(): Promise<{
|
|
104
|
+
ok: boolean;
|
|
105
|
+
compacted: boolean;
|
|
106
|
+
}>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* OpenClaw plugin definition. The object form provides declarative metadata
|
|
110
|
+
* (id, name, configSchema) that OpenClaw can read without executing code.
|
|
111
|
+
* register() is called once per agent session with a fresh api object.
|
|
112
|
+
* Each call creates isolated closures (db, sessionId, hooks) — no shared state.
|
|
113
|
+
*/
|
|
114
|
+
declare const _default: {
|
|
115
|
+
id: string;
|
|
116
|
+
name: string;
|
|
117
|
+
configSchema: {
|
|
118
|
+
type: "object";
|
|
119
|
+
properties: {
|
|
120
|
+
enabled: {
|
|
121
|
+
type: "boolean";
|
|
122
|
+
default: boolean;
|
|
123
|
+
description: string;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
additionalProperties: boolean;
|
|
127
|
+
};
|
|
128
|
+
register(api: OpenClawPluginApi): void;
|
|
129
|
+
};
|
|
130
|
+
export default _default;
|